gi-server.lisp (10873B)
1 ;;; Copyright (C) 2015 Tomas Hlavaty <tom@logand.com> 2 ;;; 3 ;;; Permission is hereby granted, free of charge, to any person 4 ;;; obtaining a copy of this software and associated documentation 5 ;;; files (the "Software"), to deal in the Software without 6 ;;; restriction, including without limitation the rights to use, copy, 7 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies 8 ;;; of the Software, and to permit persons to whom the Software is 9 ;;; furnished to do so, subject to the following conditions: 10 ;;; 11 ;;; The above copyright notice and this permission notice shall be 12 ;;; included in all copies or substantial portions of the Software. 13 ;;; 14 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 18 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 ;;; DEALINGS IN THE SOFTWARE. 22 23 ;; TODO zombies 24 ;; TODO invoke return values 25 ;; TODO handler args 26 27 (defpackage :gi-server 28 (:use :cl)) 29 30 (in-package :gi-server) 31 32 (defparameter *gi-server-program* "gi-server") 33 34 ;; TODO custom error type 35 ;;(define-condition gi-server-error (error) ()) 36 37 (defvar *handlers*) ;; id -> thunk 38 39 (defvar *istream*) 40 (defvar *ostream*) 41 42 (defun call-with-gi-server (cmd args thunk) 43 (rw.os:with-program-io (*istream* *ostream* (rw.os:make-program 44 :stream :stream cmd args nil)) 45 (let ((*handlers* (make-hash-table))) 46 (funcall thunk)))) 47 48 (defmacro with-gi-server ((cmd &optional args) &body body) 49 `(call-with-gi-server ,cmd ,args (lambda () ,@body))) 50 51 (defun serialize (x) 52 (if (atom x) 53 (etypecase x 54 (null) 55 (integer 56 (format t "~d" x)) 57 (symbol 58 (format t "~(~a~)" x)) 59 (string 60 (format t "\"") 61 (loop 62 for c across x 63 do (case c 64 (#\" (format t "\\\"")) 65 (#\\ (format t "\\\\")) 66 (t (write-char c)))) 67 (write-char #\")) 68 (function 69 (format t "~d" (funcall x 'pointer)))) 70 (progn 71 (serialize (car x)) 72 (write-char #\space) 73 (serialize (cdr x))))) 74 75 ;;(serialize '(require "GLib" "2.0")) 76 77 (defparameter *debug-request* t) 78 79 (defun request (&rest x) 80 (when *debug-request* 81 (serialize x) 82 (terpri) 83 (finish-output)) 84 (let ((*standard-output* *istream*)) 85 (serialize x)) 86 (terpri *istream*) 87 (finish-output *istream*) 88 (let* ((s (read-line *ostream*)) 89 (r (rw:peek-reader (rw:reader s))) 90 (z (rw:till r '(#\space)))) 91 (when *debug-request* 92 (format t "-> ~a~%" s) 93 (finish-output)) 94 (cond 95 ((equal z '(#\o #\k)) 96 (rw:skip r) 97 (let ((x (rw:till r))) 98 (when x 99 (coerce x 'string)))) 100 ((equal z '(#\e #\r #\r #\o #\r)) 101 (rw:skip r) 102 (error #+nil 'gi-server-error (coerce (rw:till r) 'string)))))) 103 104 (defun g_irepository_require (namespace &optional version) 105 (request 'require namespace version) 106 (values)) 107 108 (defun g_irepository_find_by_name (namespace name) 109 (parse-integer (request 'find namespace name))) 110 111 (defun g_function_info_invoke (function arguments) 112 (let ((z (request 'invoke function arguments))) 113 (when z 114 (parse-integer z)))) 115 116 (defun g_base_info_unref (pointer) 117 (request 'unref pointer) 118 (values)) 119 120 (defun g_base_info_get_type (pointer) 121 (ecase (parse-integer (request 'type pointer)) 122 (1 :function) 123 (7 :object))) 124 125 (defun g_function_info_get_flags (pointer) 126 (let ((x (parse-integer (request 'flags pointer)))) 127 (append (unless (zerop (logand x #x01)) '(:method)) 128 (unless (zerop (logand x #x02)) '(:constructor)) 129 (unless (zerop (logand x #x04)) '(:getter)) 130 (unless (zerop (logand x #x08)) '(:setter)) 131 (unless (zerop (logand x #x10)) '(:vfunc)) 132 (unless (zerop (logand x #x20)) '(:throws))))) 133 134 (defun g_object_info_find_method (pointer name) 135 (prog1 (parse-integer (request 'method pointer name)))) 136 137 (defmacro with-ref ((var val) &body body) 138 `(let ((,var ,val)) 139 (unwind-protect (locally ,@body) 140 #+nil(g_base_info_unref ,var)))) 141 142 (defun make-object (pointer &optional class) 143 (let ((class (or class 144 ;; TODO compute class from pointer 145 #+nil(parse-integer (request 'class pointer))))) 146 (lambda (method &rest args) 147 (case method 148 (class class) 149 (pointer pointer) 150 (t (with-ref (m (g_object_info_find_method class method)) 151 (assert (not (member :constructor (g_function_info_get_flags m)))) 152 (g_function_info_invoke m (cons pointer args)))))))) 153 154 (defun make-repository (namespace &optional version) 155 (g_irepository_require namespace (or version 'null)) 156 (lambda (name &rest args) 157 (with-ref (p (g_irepository_find_by_name namespace name)) 158 (ecase (g_base_info_get_type p) 159 (:function 160 (g_function_info_invoke p args)) 161 (:object 162 (destructuring-bind (method &rest arguments) args 163 (with-ref (m (g_object_info_find_method p method)) 164 (assert (member :constructor (g_function_info_get_flags m))) 165 (make-object (g_function_info_invoke m arguments) p)))))))) 166 167 (defun connect (object signal handler &optional after) 168 (let* ((s (request 'connect object signal (if after 1 0))) 169 (r (rw:peek-reader (rw:reader s))) 170 (id (parse-integer (coerce (rw:till r) 'string)))) 171 (setf (gethash id *handlers*) handler)) 172 (values)) 173 174 (defun callback () 175 (let ((k (parse-integer (request 'callback)))) 176 (or (zerop k) 177 (funcall (gethash k *handlers*)) 178 #+nil 179 (let ((v (gethash k *handlers*))) 180 (when v 181 (funcall v)))))) 182 183 (defun demo1 () 184 (with-gi-server (*gi-server-program*) 185 (funcall (make-repository "GLib" "2.0") 186 'assertion_message 187 'domain "glib-print.c" 30 'main "hello world"))) 188 189 (defun demo2 () 190 (with-gi-server (*gi-server-program*) 191 (let ((gtk (make-repository "Gtk"))) 192 (funcall gtk 'init 0 'null) 193 (let ((w (funcall gtk "Window" 'new 0))) 194 (connect w 'destroy (lambda (&rest args) 195 (declare (ignore args)) 196 (request 'exit))) 197 (funcall w "show")) 198 (loop 199 do (funcall gtk 'main_iteration) 200 while (callback))))) 201 202 (defun demo3 () 203 (with-gi-server (*gi-server-program*) 204 (let ((gtk (make-repository "Gtk"))) 205 (funcall gtk 'init 0 'null) 206 (let ((w (funcall gtk "Window" 'new 0))) 207 (funcall w 'set_title "Hello world") 208 (funcall w 'set_default_size 400 300) 209 (connect w 'destroy (lambda (&rest args) 210 (declare (ignore args)) 211 (request 'exit))) 212 (funcall w 'show_all)) 213 (loop 214 do (funcall gtk 'main_iteration) 215 while (callback))))) 216 217 (defun demo4 () 218 (with-gi-server (*gi-server-program*) 219 (let ((gtk (make-repository "Gtk"))) 220 (funcall gtk 'init 0 'null) 221 (let ((w (funcall gtk "Window" 'new 0))) 222 (funcall w 'set_border_width 10) 223 (funcall w 'set_title "Hello world") 224 (connect w 'destroy (lambda (&rest args) 225 (declare (ignore args)) 226 (request 'exit))) 227 (let ((b (funcall gtk "Button" 'new_with_label "Hello World"))) 228 (connect b 'clicked 229 (let ((i 0)) 230 (lambda (&rest args) 231 (funcall w 'set_title 232 (format nil "hi ~s ~s" (incf i) args))))) 233 (funcall w 'add b)) 234 (funcall w 'show_all)) 235 (loop 236 do (funcall gtk 'main_iteration) 237 while (callback))))) 238 239 (defun connect-glade-signals (glade fn) 240 (let (*object*) 241 (declare (special *object*)) 242 (labels ((rec (x) 243 (when (consp x) 244 (let* ((head (car x)) 245 (tail (cdr x)) 246 (plain (atom head)) 247 (e (if plain head (car head))) 248 (a (unless plain (cdr head)))) 249 (case e 250 (:|object| 251 (let ((*object* a)) 252 (declare (special *object*)) 253 (mapc #'rec tail))) 254 (:|signal| 255 (funcall fn 256 (getf *object* :|class|) 257 (getf *object* :|id|) 258 (getf a :|name|) 259 (getf a :|handler|) 260 (getf a :|swapped|))) 261 (t (mapc #'rec tail))))))) 262 (rec (rw.xml:parse-xml glade))))) 263 264 ;;https://python-gtk-3-tutorial.readthedocs.org/en/latest/builder.html 265 (defun demo5 () 266 (with-gi-server (*gi-server-program*) 267 (let ((glade (merge-pathnames "gi-server-demo5.glade" 268 (or *compile-file-pathname* 269 *load-pathname*))) 270 (gtk (make-repository "Gtk"))) 271 (funcall gtk 'init 0 'null) 272 (let ((b (funcall gtk "Builder" 'new))) 273 (funcall b 'add_from_file (namestring glade)) 274 ;; TODO when retval is object 275 (let ((w (make-object (funcall b 'get_object "window1") 276 ;; TODO automatically 277 (g_irepository_find_by_name "Gtk" "Window"))) 278 (b (make-object (funcall b 'get_object "button1") 279 ;; TODO automatically 280 (g_irepository_find_by_name "Gtk" "Button")))) 281 (connect-glade-signals 282 glade 283 (lambda (class id signal handler swapped) 284 ;;(print (list :@@@ class id signal handler swapped)) 285 ;;strip_identifier 286 (cond 287 ((string= handler "onDeleteWindow") 288 (connect w signal ;;'destroy ;;delete-event ;; TODO fix callback 289 (lambda (&rest args) 290 (declare (ignore args)) 291 (request 'exit)))) 292 ((string= handler "onButtonPressed") 293 (connect b signal 294 (let ((i 0)) 295 (lambda (&rest args) 296 (funcall w 'set_title 297 (format nil "hi ~s ~s" (incf i) args))))))))) 298 (funcall w 'show_all))) 299 (loop 300 do (funcall gtk 'main_iteration) 301 while (callback)))))