chiark / gitweb /
Added new function SIGNAL-NEW and new keyword argument to SIGNAL-CONNECT
[clg] / glib / gcallback.lisp
CommitLineData
112ac1d3 1;; Common Lisp bindings for GTK+ v2.x
2;; Copyright 2000 Espen S. Johnsen <espen@users.sf.net>
c8c48a4c 3;;
112ac1d3 4;; Permission is hereby granted, free of charge, to any person obtaining
5;; a copy of this software and associated documentation files (the
6;; "Software"), to deal in the Software without restriction, including
7;; without limitation the rights to use, copy, modify, merge, publish,
8;; distribute, sublicense, and/or sell copies of the Software, and to
9;; permit persons to whom the Software is furnished to do so, subject to
10;; the following conditions:
c8c48a4c 11;;
112ac1d3 12;; The above copyright notice and this permission notice shall be
13;; included in all copies or substantial portions of the Software.
c8c48a4c 14;;
112ac1d3 15;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
457f874e 23;; $Id: gcallback.lisp,v 1.39 2007-01-07 20:03:51 espen Exp $
c8c48a4c 24
25(in-package "GLIB")
26
27(use-prefix "g")
28
29
457f874e 30;;;; Callback invocation
c8c48a4c 31
6851535e 32(deftype gclosure () 'pointer)
33(register-type 'gclosure '|g_closure_get_type|)
34
e378b861 35(defun register-callback-function (function)
36 (check-type function (or null symbol function))
37 (register-user-data function))
c8c48a4c 38
e7225d0f 39;; Callback marshaller for regular signal handlers
40(define-callback signal-handler-marshal nil
56ccd5b7 41 ((gclosure gclosure) (return-value gvalue) (n-params unsigned-int)
42 (param-values pointer) (invocation-hint pointer)
e7225d0f 43 (callback-id unsigned-long))
266ca870 44 (declare (ignore gclosure invocation-hint))
e7225d0f 45 (callback-trampoline #'invoke-signal-handler callback-id n-params param-values return-value))
c8c48a4c 46
e7225d0f 47;; Callback marshaller for class handlers
48(define-callback class-handler-marshal nil
49 ((gclosure gclosure) (return-value gvalue) (n-params unsigned-int)
50 (param-values pointer) (invocation-hint pointer)
51 (callback-id unsigned-long))
52 (declare (ignore gclosure invocation-hint))
53 (callback-trampoline #'invoke-callback callback-id n-params param-values return-value))
54
55;; Callback marshaller for emission hooks
56(define-callback emission-hook-marshal nil
56ccd5b7 57 ((invocation-hint pointer) (n-params unsigned-int) (param-values pointer)
e7225d0f 58 (callback-id unsigned-long))
5de2b5f6 59 (declare (ignore invocation-hint))
e7225d0f 60 (callback-trampoline #'invoke-callback callback-id n-params param-values))
e0d2987b 61
e7225d0f 62(defun callback-trampoline (restart-wrapper callback-id n-params param-values
63 &optional (return-value (make-pointer 0)))
c8c48a4c 64 (let* ((return-type (unless (null-pointer-p return-value)
e378b861 65 (gvalue-type return-value)))
34f9e1d4 66 (args (loop
67 for n from 0 below n-params
832df308 68 for offset from 0 by +gvalue-size+
77a843ca 69 collect (gvalue-peek (pointer+ param-values offset)))))
832df308 70 (unwind-protect
e7225d0f 71 (multiple-value-bind (result aborted-p)
72 (apply restart-wrapper callback-id nil args)
73 (when (and return-type (not aborted-p))
832df308 74 (gvalue-set return-value result)))
77a843ca 75 ;; TODO: this should be made more general, by adding a type
e7225d0f 76 ;; method to return invalidating functions.
832df308 77 (loop
78 for arg in args
77a843ca 79 when (typep arg 'struct)
832df308 80 do (invalidate-instance arg)))))
81
e7225d0f 82(defun invoke-signal-handler (callback-id return-type &rest args)
83 (declare (ignore return-type))
84 (let* ((instance (first args))
85 (handler-id (signal-handler-find instance '(:data)
86 0 0 nil nil callback-id)))
87 (signal-handler-block instance handler-id)
88 (unwind-protect
89 (restart-case (apply #'invoke-callback callback-id nil args)
90 (abort () :report "Disconnect and exit signal handler"
91 (when (signal-handler-is-connected-p instance handler-id)
92 (signal-handler-disconnect instance handler-id))
93 (values nil t))))
94 (when (signal-handler-is-connected-p instance handler-id)
95 (signal-handler-unblock instance handler-id))))
34f9e1d4 96
8755b1a5 97(defun invoke-callback (callback-id return-type &rest args)
e7225d0f 98 (restart-case (apply (find-user-data callback-id) args)
34f9e1d4 99 (continue nil :report "Return from callback function"
e7225d0f 100 (cond
101 (return-type
102 (format *query-io* "Enter return value of type ~S: " return-type)
103 (force-output *query-io*)
104 (eval (read *query-io*)))
105 (t (values nil t))))
34f9e1d4 106 (re-invoke nil :report "Re-invoke callback function"
e7225d0f 107 (apply #'invoke-callback callback-id return-type args))))
c8c48a4c 108
c8c48a4c 109
e378b861 110;;;; Timeouts and idle functions
111
acd28982 112(defconstant +priority-high+ -100)
113(defconstant +priority-default+ 0)
114(defconstant +priority-high-idle+ 100)
115(defconstant +priority-default-idle+ 200)
116(defconstant +priority-low+ 300)
117
118(defbinding source-remove () boolean
119 (tag unsigned-int))
120
56ccd5b7 121(define-callback source-callback-marshal nil ((callback-id unsigned-int))
afa01e1b 122 (callback-trampoline #'invoke-callback callback-id 0 nil))
e378b861 123
124(defbinding (timeout-add "g_timeout_add_full")
acd28982 125 (interval function &optional (priority +priority-default+)) unsigned-int
e378b861 126 (priority int)
127 (interval unsigned-int)
56ccd5b7 128 (source-callback-marshal callback)
e378b861 129 ((register-callback-function function) unsigned-long)
56ccd5b7 130 (user-data-destroy-callback callback))
e378b861 131
acd28982 132(defun timeout-remove (timeout)
133 (source-remove timeout))
134
e378b861 135(defbinding (idle-add "g_idle_add_full")
acd28982 136 (function &optional (priority +priority-default-idle+)) unsigned-int
e378b861 137 (priority int)
56ccd5b7 138 (source-callback-marshal callback)
e378b861 139 ((register-callback-function function) unsigned-long)
56ccd5b7 140 (user-data-destroy-callback callback))
e378b861 141
acd28982 142(defun idle-remove (idle)
143 (source-remove idle))
e378b861 144
c8c48a4c 145
e0d2987b 146;;;; Signal information querying
c8c48a4c 147
e0d2987b 148(defbinding signal-lookup (name type) unsigned-int
c8c48a4c 149 ((signal-name-to-string name) string)
e0d2987b 150 ((find-type-number type t) type-number))
c8c48a4c 151
e0d2987b 152(defbinding signal-name () (copy-of string)
c8c48a4c 153 (signal-id unsigned-int))
154
e0d2987b 155(defbinding signal-list-ids (type) (vector unsigned-int n-ids)
156 ((find-type-number type t) type-number)
157 (n-ids unsigned-int :out))
158
159(defun signal-list-names (type)
160 (map 'list #'signal-name (signal-list-ids type)))
161
162(defun ensure-signal-id-from-type (signal-id type)
c8c48a4c 163 (etypecase signal-id
e0d2987b 164 (integer (if (signal-name signal-id)
165 signal-id
166 (error "Invalid signal id: ~D" signal-id)))
167 ((or symbol string)
168 (let ((numeric-id (signal-lookup signal-id type)))
169 (if (zerop numeric-id)
170 (error "Invalid signal name for ~S: ~D" type signal-id)
171 numeric-id)))))
172
173(defun ensure-signal-id (signal-id instance)
174 (ensure-signal-id-from-type signal-id (type-of instance)))
c8c48a4c 175
e0d2987b 176(eval-when (:compile-toplevel :load-toplevel :execute)
177 (deftype signal-flags ()
178 '(flags :run-first :run-last :run-cleanup :no-recurse
179 :detailed :action :no-hooks))
180
e7225d0f 181 (define-flags-type signal-match-type
182 :id :detail :closure :func :data :unblocked)
183
e0d2987b 184 (defclass signal-query (struct)
185 ((id :allocation :alien :type unsigned-int)
186 (name :allocation :alien :type (copy-of string))
187 (type :allocation :alien :type type-number)
188 (flags :allocation :alien :type signal-flags)
189 (return-type :allocation :alien :type type-number)
190 (n-params :allocation :alien :type unsigned-int)
191 (param-types :allocation :alien :type pointer))
192 (:metaclass struct-class)))
193
194(defbinding signal-query
195 (signal-id &optional (signal-query (make-instance 'signal-query))) nil
196 (signal-id unsigned-int)
77a843ca 197 (signal-query signal-query :in/return))
e0d2987b 198
199(defun signal-param-types (info)
200 (with-slots (n-params param-types) info
201 (map-c-vector 'list
202 #'(lambda (type-number)
203 (type-from-number type-number))
204 param-types 'type-number n-params)))
205
206
207(defun describe-signal (signal-id &optional type)
208 (let ((info (signal-query (ensure-signal-id-from-type signal-id type))))
209 (with-slots (id name type flags return-type n-params) info
457f874e 210 (format t "The signal with id ~D is named '~A' and may be emitted on instances of type ~S." id name (type-from-number type t))
211 (when flags
212 (format t " It has the followin invocation flags: ~{~S ~}" flags))
213 (format t "~%~%Signal handlers should take ~A and return ~A~%"
e0d2987b 214 (if (zerop n-params)
215 "no arguments"
216 (format nil "arguments with the following types: ~A"
457f874e 217 (signal-param-types info)))
218 (cond
219 ((= return-type (find-type-number "void")) "no values")
220 ((not (type-from-number return-type)) "values of unknown type")
221 ((format nil "values of type ~S" (type-from-number return-type))))))))
e0d2987b 222
223
224;;;; Signal connecting and controlling
225
9944c385 226(defvar *overridden-signals* (make-hash-table :test 'equalp))
227
228(defbinding %signal-override-class-closure () nil
229 (signal-id unsigned-int)
230 (type-number type-number)
231 (callback-closure pointer))
232
233
234(defun signal-override-class-closure (name type function)
235 (let* ((signal-id (ensure-signal-id-from-type name type))
236 (type-number (find-type-number type t))
237 (callback-id (gethash (cons type-number signal-id) *overridden-signals*)))
238 (if callback-id
239 (update-user-data callback-id function)
240 (multiple-value-bind (callback-closure callback-id)
e7225d0f 241 (make-callback-closure function class-handler-marshal)
9944c385 242 (%signal-override-class-closure signal-id type-number callback-closure)
243 (setf
244 (gethash (cons type-number signal-id) *overridden-signals*)
245 callback-id)))))
246
247
248(defbinding %signal-chain-from-overridden () nil
249 (args pointer)
250 (return-value (or null gvalue)))
251
38049b1a 252
253(defun %call-next-handler (n-params types args return-type)
9944c385 254 (let ((params (allocate-memory (* n-params +gvalue-size+))))
255 (loop
38049b1a 256 for arg in args
9944c385 257 for type in types
258 for offset from 0 by +gvalue-size+
77a843ca 259 do (gvalue-init (pointer+ params offset) type arg))
9944c385 260
261 (unwind-protect
262 (if return-type
263 (with-gvalue (return-value return-type)
264 (%signal-chain-from-overridden params return-value))
265 (%signal-chain-from-overridden params nil))
266 (progn
267 (loop
268 repeat n-params
269 for offset from 0 by +gvalue-size+
77a843ca 270 do (gvalue-unset (pointer+ params offset)))
9944c385 271 (deallocate-memory params)))))
272
273
274(defmacro define-signal-handler (name ((object class) &rest args) &body body)
275 (let* ((info (signal-query (ensure-signal-id-from-type name class)))
276 (types (cons class (signal-param-types info)))
277 (n-params (1+ (slot-value info 'n-params)))
278 (return-type (type-from-number (slot-value info 'return-type)))
279 (vars (loop
280 for arg in args
281 until (eq arg '&rest)
282 collect arg))
283 (rest (cadr (member '&rest args)))
38049b1a 284 (next (make-symbol "ARGS"))
285 (default (make-symbol "DEFAULT")))
9944c385 286
287 `(progn
288 (signal-override-class-closure ',name ',class
289 #'(lambda (,object ,@args)
38049b1a 290 (let ((,default (list* ,object ,@vars ,rest)))
291 (flet ((call-next-handler (&rest ,next)
9944c385 292 (%call-next-handler
e1fcda22 293 ,n-params ',types (or ,next ,default) ',return-type)))
294 ,@body))))
9944c385 295 ',name)))
296
297
e0d2987b 298(defbinding %signal-stop-emission () nil
c8c48a4c 299 (instance ginstance)
e0d2987b 300 (signal-id unsigned-int)
301 (detail quark))
302
303(defvar *signal-stop-emission* nil)
304(declaim (special *signal-stop-emission*))
c8c48a4c 305
e0d2987b 306(defun signal-stop-emission ()
307 (if *signal-stop-emission*
308 (funcall *signal-stop-emission*)
309 (error "Not inside a signal handler")))
310
311
312(defbinding signal-add-emission-hook (type signal function &key (detail 0))
e7225d0f 313 unsigned-long
e0d2987b 314 ((ensure-signal-id-from-type signal type) unsigned-int)
315 (detail quark)
e7225d0f 316 (emission-hook-marshal callback)
e0d2987b 317 ((register-callback-function function) unsigned-int)
56ccd5b7 318 (user-data-destroy-callback callback))
e0d2987b 319
320(defbinding signal-remove-emission-hook (type signal hook-id) nil
321 ((ensure-signal-id-from-type signal type) unsigned-int)
e7225d0f 322 (hook-id unsigned-long))
c8c48a4c 323
c8c48a4c 324
0383dd48 325(defbinding (signal-has-handler-pending-p "g_signal_has_handler_pending")
c8c48a4c 326 (instance signal-id &key detail blocked) boolean
327 (instance ginstance)
e49e135a 328 ((ensure-signal-id signal-id instance) unsigned-int)
c8c48a4c 329 ((or detail 0) quark)
73572c12 330 (blocked boolean))
c8c48a4c 331
e7225d0f 332(defbinding %signal-connect-closure-by-id () unsigned-long
c8c48a4c 333 (instance ginstance)
e0d2987b 334 (signal-id unsigned-int)
335 (detail quark)
336 (closure pointer)
c8c48a4c 337 (after boolean))
338
0383dd48 339(defbinding signal-handler-block () nil
c8c48a4c 340 (instance ginstance)
e7225d0f 341 (handler-id unsigned-long))
c8c48a4c 342
0383dd48 343(defbinding signal-handler-unblock () nil
c8c48a4c 344 (instance ginstance)
e7225d0f 345 (handler-id unsigned-long))
346
347;; Internal
348(defbinding signal-handler-find () unsigned-long
349 (instance gobject)
350 (mask signal-match-type)
351 (signal-id unsigned-int)
352 (detail quark)
353 (closure (or null pointer))
354 (func (or null pointer))
355 (data unsigned-long))
c8c48a4c 356
0383dd48 357(defbinding signal-handler-disconnect () nil
c8c48a4c 358 (instance ginstance)
e7225d0f 359 (handler-id unsigned-long))
e0d2987b 360
361(defbinding signal-handler-is-connected-p () boolean
362 (instance ginstance)
e7225d0f 363 (handler-id unsigned-long))
c8c48a4c 364
d4f4418a 365(defbinding (closure-new "g_cclosure_new") () gclosure
366 ((make-pointer #xFFFFFFFF) pointer)
e0d2987b 367 (callback-id unsigned-int)
56ccd5b7 368 (destroy-notify callback))
c8c48a4c 369
d4f4418a 370(defbinding closure-set-meta-marshal () nil
371 (gclosure gclosure)
372 (callback-id unsigned-int)
373 (callback callback))
374
375(defun callback-closure-new (callback-id callback destroy-notify)
376 (let ((gclosure (closure-new callback-id destroy-notify)))
377 (closure-set-meta-marshal gclosure callback-id callback)
378 gclosure))
379
e7225d0f 380(defun make-callback-closure (function marshaller)
e0d2987b 381 (let ((callback-id (register-callback-function function)))
382 (values
e7225d0f 383 (callback-closure-new callback-id marshaller user-data-destroy-callback)
e0d2987b 384 callback-id)))
385
457f874e 386(defgeneric compute-signal-function (gobject signal function object args))
65670fe5 387
457f874e 388(defmethod compute-signal-function ((gobject gobject) signal function object args)
cd9b9e8b 389 (declare (ignore signal))
e0d2987b 390 (cond
457f874e 391 ((or (eq object t) (eq object gobject))
392 (if args
393 #'(lambda (&rest emission-args)
394 (apply function (nconc emission-args args)))
395 function))
396 (object
397 (if args
398 #'(lambda (&rest emission-args)
399 (apply function object (nconc (rest emission-args) args)))
400 #'(lambda (&rest emission-args)
401 (apply function object (rest emission-args)))))
402 (args
403 #'(lambda (&rest emission-args)
404 (apply function (nconc (rest emission-args) args))))
e0d2987b 405 (t
457f874e 406 #'(lambda (&rest emission-args)
407 (apply function (rest emission-args))))))
cd9b9e8b 408
409(defgeneric compute-signal-id (gobject signal))
410
411(defmethod compute-signal-id ((gobject gobject) signal)
412 (ensure-signal-id signal gobject))
413
414
457f874e 415(defgeneric signal-connect (gobject signal function &key detail after object remove args))
cd9b9e8b 416
417(defmethod signal-connect :around ((gobject gobject) signal function &rest args)
418 (declare (ignore gobject signal args))
419 (when function
420 (call-next-method)))
e0d2987b 421
65670fe5 422
e0d2987b 423(defmethod signal-connect ((gobject gobject) signal function
457f874e 424 &key detail after object remove args)
e0d2987b 425"Connects a callback function to a signal for a particular object. If
426:OBJECT is T, the object connected to is passed as the first argument
427to the callback function, or if :OBJECT is any other non NIL value, it
428is passed as the first argument instead. If :AFTER is non NIL, the
429handler will be called after the default handler for the signal. If
430:REMOVE is non NIL, the handler will be removed after beeing invoked
457f874e 431once. ARGS is a list of additional arguments passed to the callback
432function."
cd9b9e8b 433(let* ((signal-id (compute-signal-id gobject signal))
434 (detail-quark (if detail (quark-intern detail) 0))
435 (signal-stop-emission
436 #'(lambda ()
437 (%signal-stop-emission gobject signal-id detail-quark)))
457f874e 438 (callback (compute-signal-function gobject signal function object args))
cd9b9e8b 439 (wrapper #'(lambda (&rest args)
440 (let ((*signal-stop-emission* signal-stop-emission))
441 (apply callback args)))))
e0d2987b 442 (multiple-value-bind (closure-id callback-id)
e7225d0f 443 (make-callback-closure wrapper signal-handler-marshal)
e0d2987b 444 (let ((handler-id (%signal-connect-closure-by-id
cd9b9e8b 445 gobject signal-id detail-quark closure-id after)))
e0d2987b 446 (when remove
447 (update-user-data callback-id
448 #'(lambda (&rest args)
449 (unwind-protect
450 (let ((*signal-stop-emission* signal-stop-emission))
451 (apply callback args))
e7225d0f 452 (when (signal-handler-is-connected-p gobject handler-id)
453 (signal-handler-disconnect gobject handler-id))))))
cd9b9e8b 454 handler-id))))
e0d2987b 455
456
457;;;; Signal emission
458
459(defbinding %signal-emitv () nil
460 (gvalues pointer)
461 (signal-id unsigned-int)
462 (detail quark)
463 (return-value gvalue))
464
465(defvar *signal-emit-functions* (make-hash-table))
466
467(defun create-signal-emit-function (signal-id)
468 (let ((info (signal-query signal-id)))
469 (let* ((type (type-from-number (slot-value info 'type)))
470 (param-types (cons type (signal-param-types info)))
471 (return-type (type-from-number (slot-value info 'return-type)))
472 (n-params (1+ (slot-value info 'n-params)))
473 (params (allocate-memory (* n-params +gvalue-size+))))
474 #'(lambda (detail object &rest args)
475 (unless (= (length args) (1- n-params))
476 (error "Invalid number of arguments: ~A" (+ 2 (length args))))
477 (unwind-protect
478 (loop
479 for arg in (cons object args)
480 for type in param-types
77a843ca 481 as tmp = params then (pointer+ tmp +gvalue-size+)
e0d2987b 482 do (gvalue-init tmp type arg)
483 finally
484 (if return-type
485 (return
486 (with-gvalue (return-value)
487 (%signal-emitv params signal-id detail return-value)))
488 (%signal-emitv params signal-id detail (make-pointer 0))))
489 (loop
490 repeat n-params
77a843ca 491 as tmp = params then (pointer+ tmp +gvalue-size+)
e0d2987b 492 while (gvalue-p tmp)
493 do (gvalue-unset tmp)))))))
494
495(defun signal-emit-with-detail (object signal detail &rest args)
496 (let* ((signal-id (ensure-signal-id signal object))
497 (function (or
498 (gethash signal-id *signal-emit-functions*)
499 (setf
500 (gethash signal-id *signal-emit-functions*)
501 (create-signal-emit-function signal-id)))))
502 (apply function detail object args)))
503
504(defun signal-emit (object signal &rest args)
505 (apply #'signal-emit-with-detail object signal 0 args))
506
c0f178d0 507
457f874e 508;;;; Signal registration
509
510(defbinding %signal-newv (name itype flags return-type param-types)
511 unsigned-int
512 ((signal-name-to-string name) string)
513 (itype gtype)
514 (flags signal-flags)
515 (nil null) ; class closure
516 (nil null) ; accumulator
517 (nil null) ; accumulator data
518 (nil null) ; c marshaller
519 (return-type gtype)
520 ((length param-types) unsigned-int)
521 (param-types (vector gtype)))
522
523(defun signal-new (name itype flags return-type param-types)
524 (when (zerop (signal-lookup name itype))
525 (%signal-newv name itype flags return-type param-types)))
526
fd1e4a39 527;;;; Convenient macros
528
56ccd5b7 529(defmacro define-callback-marshal (name return-type args &key (callback-id :last))
530 (let* ((ignore ())
531 (params ())
532 (names (loop
533 for arg in args
534 collect (if (or
535 (eq arg :ignore)
536 (and (consp arg) (eq (first arg) :ignore)))
537 (let ((name (gensym "IGNORE")))
538 (push name ignore)
539 name)
540 (let ((name (if (atom arg)
541 (gensym (string arg))
542 (first arg))))
543 (push name params)
544 name))))
545 (types (loop
546 for arg in args
547 collect (cond
548 ((eq arg :ignore) 'pointer)
549 ((atom arg) arg)
550 (t (second arg))))))
551 `(define-callback ,name ,return-type
552 ,(ecase callback-id
553 (:first `((callback-id unsigned-int) ,@(mapcar #'list names types)))
554 (:last `(,@(mapcar #'list names types) (callback-id unsigned-int))))
555 (declare (ignore ,@ignore))
248f4dd7 556 (invoke-callback callback-id ',return-type ,@(nreverse params)))))
fd1e4a39 557
558(defmacro with-callback-function ((id function) &body body)
559 `(let ((,id (register-callback-function ,function)))
560 (unwind-protect
561 (progn ,@body)
562 (destroy-user-data ,id))))