src/xlib-util.lisp (event-hook-name): Intern hook name symbole in :clfswm package.
[clfswm.git] / src / xlib-util.lisp
blob308a9881f067de560d40a15fdbf7b3f75f67af49
1 ;;; --------------------------------------------------------------------------
2 ;;; CLFSWM - FullScreen Window Manager
3 ;;;
4 ;;; --------------------------------------------------------------------------
5 ;;; Documentation: Utility functions
6 ;;; --------------------------------------------------------------------------
7 ;;;
8 ;;; (C) 2012 Philippe Brochard <pbrochard@common-lisp.net>
9 ;;;
10 ;;; This program is free software; you can redistribute it and/or modify
11 ;;; it under the terms of the GNU General Public License as published by
12 ;;; the Free Software Foundation; either version 3 of the License, or
13 ;;; (at your option) any later version.
14 ;;;
15 ;;; This program is distributed in the hope that it will be useful,
16 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;;; GNU General Public License for more details.
19 ;;;
20 ;;; You should have received a copy of the GNU General Public License
21 ;;; along with this program; if not, write to the Free Software
22 ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 ;;;
24 ;;; --------------------------------------------------------------------------
26 (in-package :clfswm)
28 ;; Window states
29 (defconstant +withdrawn-state+ 0)
30 (defconstant +normal-state+ 1)
31 (defconstant +iconic-state+ 3)
34 (defparameter *window-events* '(:structure-notify
35 :property-change
36 :colormap-change
37 :focus-change
38 :enter-window
39 :leave-window
40 :pointer-motion
41 :exposure)
42 "The events to listen for on managed windows.")
45 (defparameter +netwm-supported+
46 '(:_NET_SUPPORTING_WM_CHECK
47 :_NET_NUMBER_OF_DESKTOPS
48 :_NET_DESKTOP_GEOMETRY
49 :_NET_DESKTOP_VIEWPORT
50 :_NET_CURRENT_DESKTOP
51 :_NET_WM_WINDOW_TYPE
52 :_NET_CLIENT_LIST)
53 "Supported NETWM properties.
54 Window types are in +WINDOW-TYPES+.")
56 (defparameter +netwm-window-types+
57 '((:_NET_WM_WINDOW_TYPE_DESKTOP . :desktop)
58 (:_NET_WM_WINDOW_TYPE_DOCK . :dock)
59 (:_NET_WM_WINDOW_TYPE_TOOLBAR . :toolbar)
60 (:_NET_WM_WINDOW_TYPE_MENU . :menu)
61 (:_NET_WM_WINDOW_TYPE_UTILITY . :utility)
62 (:_NET_WM_WINDOW_TYPE_SPLASH . :splash)
63 (:_NET_WM_WINDOW_TYPE_DIALOG . :dialog)
64 (:_NET_WM_WINDOW_TYPE_NORMAL . :normal))
65 "Alist mapping NETWM window types to keywords.")
68 (defmacro with-xlib-protect (() &body body)
69 "Prevent Xlib errors"
70 `(handler-case
71 (with-simple-restart (top-level "Return to clfswm's top level")
72 ,@body)
73 ((or xlib:match-error xlib:window-error xlib:drawable-error xlib:lookup-error) (c)
74 (progn
75 (format t "Ignoring XLib error: ~S~%" c)
76 (unassoc-keyword-handle-event)
77 (assoc-keyword-handle-event 'main-mode)
78 (setf *in-second-mode* nil)))))
81 (defmacro with-x-pointer (&body body)
82 "Bind (x y) to mouse pointer positions"
83 `(multiple-value-bind (x y)
84 (xlib:query-pointer *root*)
85 ,@body))
89 (declaim (inline window-x2 window-y2))
90 (defun window-x2 (window)
91 (+ (x-drawable-x window) (x-drawable-width window)))
93 (defun window-y2 (window)
94 (+ (x-drawable-y window) (x-drawable-height window)))
98 ;;;
99 ;;; Events management functions.
101 (defparameter *unhandled-events* nil)
102 (defparameter *current-event-mode* nil)
104 (eval-when (:compile-toplevel :load-toplevel :execute)
105 (defun keyword->handle-event (mode keyword)
106 (create-symbol 'handle-event-fun "-" mode "-" keyword)))
108 (defun handle-event->keyword (symbol)
109 (let* ((name (string-downcase (symbol-name symbol)))
110 (pos (search "handle-event-fun-" name)))
111 (when (and pos (zerop pos))
112 (let ((pos-mod (search "mode" name)))
113 (when pos-mod
114 (values (intern (string-upcase (subseq name (+ pos-mod 5))) :keyword)
115 (subseq name (length "handle-event-fun-") (1- pos-mod))))))))
117 (defparameter *handle-event-fun-symbols* nil)
119 (defun fill-handle-event-fun-symbols ()
120 (with-all-internal-symbols (symbol :clfswm)
121 (let ((pos (symbol-search "handle-event-fun-" symbol)))
122 (when (and pos (zerop pos))
123 (pushnew symbol *handle-event-fun-symbols*)))))
126 (defmacro with-handle-event-symbol ((mode) &body body)
127 "Bind symbol to all handle event functions available in mode"
128 `(let ((pattern (format nil "handle-event-fun-~A" ,mode)))
129 (dolist (symbol *handle-event-fun-symbols*)
130 (let ((pos (symbol-search pattern symbol)))
131 (when (and pos (zerop pos))
132 ,@body)))))
135 (defun find-handle-event-function (&optional (mode ""))
136 "Print all handle event functions available in mode"
137 (with-handle-event-symbol (mode)
138 (print symbol)))
140 (defun assoc-keyword-handle-event (mode)
141 "Associate all keywords in mode to their corresponding handle event functions.
142 For example: main-mode :key-press is bound to handle-event-fun-main-mode-key-press"
143 (setf *current-event-mode* mode)
144 (with-handle-event-symbol (mode)
145 (let ((keyword (handle-event->keyword symbol)))
146 (when (fboundp symbol)
147 #+:event-debug
148 (progn
149 (format t "~&Associating: ~S with ~S~%" symbol keyword)
150 (force-output))
151 (setf (symbol-function keyword) (symbol-function symbol))))))
153 (defun unassoc-keyword-handle-event (&optional (mode ""))
154 "Unbound all keywords from their corresponding handle event functions."
155 (setf *current-event-mode* nil)
156 (with-handle-event-symbol (mode)
157 (let ((keyword (handle-event->keyword symbol)))
158 (when (fboundp keyword)
159 #+:event-debug
160 (progn
161 (format t "~&Unassociating: ~S ~S~%" symbol keyword)
162 (force-output))
163 (fmakunbound keyword)))))
165 (defmacro define-handler (mode keyword args &body body)
166 "Like a defun but with a name expanded as handle-event-fun-'mode'-'keyword'
167 For example (define-handler main-mode :key-press (args) ...)
168 Expand in handle-event-fun-main-mode-key-press"
169 `(defun ,(keyword->handle-event mode keyword) (&rest event-slots &key #+:event-debug event-key ,@args &allow-other-keys)
170 (declare (ignorable event-slots))
171 #+:event-debug (print (list *current-event-mode* event-key))
172 ,@body))
175 (defun event-hook-name (event-keyword)
176 (create-symbol-in-package :clfswm '*event- event-keyword '-hook*))
178 (let ((event-hook-list nil))
179 (defun get-event-hook-list ()
180 event-hook-list)
182 (defmacro use-event-hook (event-keyword)
183 (let ((symb (event-hook-name event-keyword)))
184 (pushnew symb event-hook-list)
185 `(defvar ,symb nil)))
187 (defun unuse-event-hook (event-keyword)
188 (let ((symb (event-hook-name event-keyword)))
189 (setf event-hook-list (remove symb event-hook-list))
190 (makunbound symb)))
192 (defmacro add-event-hook (name &rest value)
193 (let ((symb (event-hook-name name)))
194 `(add-hook ,symb ,@value)))
196 (defmacro remove-event-hook (name &rest value)
197 (let ((symb (event-hook-name name)))
198 `(remove-hook ,symb ,@value)))
200 (defun clear-event-hooks ()
201 (dolist (symb event-hook-list)
202 (makunbound symb)))
205 (defun optimize-event-hook ()
206 "Remove unused event hooks"
207 (dolist (symb event-hook-list)
208 (when (and (boundp symb)
209 (null (symbol-value symb)))
210 (makunbound symb)
211 (setf event-hook-list (remove symb event-hook-list))))))
215 (defmacro define-event-hook (event-keyword args &body body)
216 `(add-event-hook ,event-keyword
217 (lambda (&rest event-slots &key #+:event-debug event-key ,@args &allow-other-keys)
218 (declare (ignorable event-slots))
219 #+:event-debug (print (list ,event-keyword event-key))
220 ,@body)))
222 (defmacro event-defun (name args &body body)
223 `(defun ,name (&rest event-slots &key #+:event-debug event-key ,@args &allow-other-keys)
224 (declare (ignorable event-slots))
225 #+:event-debug (print (list ,event-keyword event-key))
226 ,@body))
228 (defun exit-handle-event ()
229 (throw 'exit-handle-event nil))
232 (defun handle-event (&rest event-slots &key event-key &allow-other-keys)
233 (labels ((make-xlib-window (xobject)
234 "For some reason the clx xid cache screws up returns pixmaps when
235 they should be windows. So use this function to make a window out of them."
236 ;; Workaround for pixmap error taken from STUMPWM - thanks:
237 ;; XXX: In both the clisp and sbcl clx libraries, sometimes what
238 ;; should be a window will be a pixmap instead. In this case, we
239 ;; need to manually translate it to a window to avoid breakage
240 ;; in stumpwm. So far the only slot that seems to be affected is
241 ;; the :window slot for configure-request and reparent-notify
242 ;; events. It appears as though the hash table of XIDs and clx
243 ;; structures gets out of sync with X or perhaps X assigns a
244 ;; duplicate ID for a pixmap and a window.
245 #+clisp (make-instance 'xlib:window :id (slot-value xobject 'xlib::id) :display *display*)
246 #+(or sbcl ecl openmcl) (xlib::make-window :id (slot-value xobject 'xlib::id) :display *display*)
247 #-(or sbcl clisp ecl openmcl)
248 (error 'not-implemented)))
249 (with-xlib-protect ()
250 (catch 'exit-handle-event
251 (let ((win (getf event-slots :window)))
252 (when (and win (not (xlib:window-p win)))
253 (dbg "Pixmap Workaround! Should be a window: " win)
254 (setf (getf event-slots :window) (make-xlib-window win))))
255 (let ((hook-symbol (event-hook-name event-key)))
256 (when (boundp hook-symbol)
257 (apply #'call-hook (symbol-value hook-symbol) event-slots)))
258 (if (fboundp event-key)
259 (apply event-key event-slots)
260 #+:event-debug (pushnew (list *current-event-mode* event-key) *unhandled-events* :test #'equal)))
261 (xlib:display-finish-output *display*))
268 (defun parse-display-string (display)
269 "Parse an X11 DISPLAY string and return the host and display from it."
270 (let* ((colon (position #\: display))
271 (host (subseq display 0 colon))
272 (rest (subseq display (1+ colon)))
273 (dot (position #\. rest))
274 (num (parse-integer (subseq rest 0 dot))))
275 (values host num)))
278 ;;; Transparency support
279 (let ((opaque #xFFFFFFFF))
280 (defun window-transparency (window)
281 "Return the window transparency"
282 (float (/ (or (first (xlib:get-property window :_NET_WM_WINDOW_OPACITY)) opaque) opaque)))
284 (defun set-window-transparency (window value)
285 "Set the window transparency"
286 (when (numberp value)
287 (xlib:change-property window :_NET_WM_WINDOW_OPACITY
288 (list (max (min (round (* opaque (if (equal *transparent-background* t) value 1)))
289 opaque)
291 :cardinal 32)))
293 (defsetf window-transparency set-window-transparency))
297 (defun window-state (win)
298 "Get the state (iconic, normal, withdrawn) of a window."
299 (first (xlib:get-property win :WM_STATE)))
302 (defun set-window-state (win state)
303 "Set the state (iconic, normal, withdrawn) of a window."
304 (xlib:change-property win
305 :WM_STATE
306 (list state)
307 :WM_STATE
308 32))
310 (defsetf window-state set-window-state)
314 (defun window-hidden-p (window)
315 (eql (window-state window) +iconic-state+))
318 (defun null-size-window-p (window)
319 (let ((hints (xlib:wm-normal-hints window)))
320 (and hints
321 (not (or (xlib:wm-size-hints-width hints)
322 (xlib:wm-size-hints-height hints)
323 (xlib:wm-size-hints-win-gravity hints)))
324 (xlib:wm-size-hints-user-specified-position-p hints))))
331 (defun unhide-window (window)
332 (when window
333 (when (window-hidden-p window)
334 (xlib:map-window window)
335 (setf (window-state window) +normal-state+
336 (xlib:window-event-mask window) *window-events*))))
339 (defun map-window (window)
340 (when window
341 (xlib:map-window window)))
344 (defun delete-window (window)
345 (send-client-message window :WM_PROTOCOLS
346 (xlib:intern-atom *display* "WM_DELETE_WINDOW")))
348 (defun destroy-window (window)
349 (xlib:kill-client *display* (xlib:window-id window)))
352 ;;(defconstant +exwm-atoms+
353 ;; (list "_NET_SUPPORTED" "_NET_CLIENT_LIST"
354 ;; "_NET_CLIENT_LIST_STACKING" "_NET_NUMBER_OF_DESKTOPS"
355 ;; "_NET_CURRENT_DESKTOP" "_NET_DESKTOP_GEOMETRY"
356 ;; "_NET_DESKTOP_VIEWPORT" "_NET_DESKTOP_NAMES"
357 ;; "_NET_ACTIVE_WINDOW" "_NET_WORKAREA"
358 ;; "_NET_SUPPORTING_WM_CHECK" "_NET_VIRTUAL_ROOTS"
359 ;; "_NET_DESKTOP_LAYOUT"
361 ;; "_NET_RESTACK_WINDOW" "_NET_REQUEST_FRAME_EXTENTS"
362 ;; "_NET_MOVERESIZE_WINDOW" "_NET_CLOSE_WINDOW"
363 ;; "_NET_WM_MOVERESIZE"
365 ;; "_NET_WM_SYNC_REQUEST" "_NET_WM_PING"
367 ;; "_NET_WM_NAME" "_NET_WM_VISIBLE_NAME"
368 ;; "_NET_WM_ICON_NAME" "_NET_WM_VISIBLE_ICON_NAME"
369 ;; "_NET_WM_DESKTOP" "_NET_WM_WINDOW_TYPE"
370 ;; "_NET_WM_STATE" "_NET_WM_STRUT"
371 ;; "_NET_WM_ICON_GEOMETRY" "_NET_WM_ICON"
372 ;; "_NET_WM_PID" "_NET_WM_HANDLED_ICONS"
373 ;; "_NET_WM_USER_TIME" "_NET_FRAME_EXTENTS"
374 ;; ;; "_NET_WM_MOVE_ACTIONS"
376 ;; "_NET_WM_WINDOW_TYPE_DESKTOP" "_NET_WM_STATE_MODAL"
377 ;; "_NET_WM_WINDOW_TYPE_DOCK" "_NET_WM_STATE_STICKY"
378 ;; "_NET_WM_WINDOW_TYPE_TOOLBAR" "_NET_WM_STATE_MAXIMIZED_VERT"
379 ;; "_NET_WM_WINDOW_TYPE_MENU" "_NET_WM_STATE_MAXIMIZED_HORZ"
380 ;; "_NET_WM_WINDOW_TYPE_UTILITY" "_NET_WM_STATE_SHADED"
381 ;; "_NET_WM_WINDOW_TYPE_SPLASH" "_NET_WM_STATE_SKIP_TASKBAR"
382 ;; "_NET_WM_WINDOW_TYPE_DIALOG" "_NET_WM_STATE_SKIP_PAGER"
383 ;; "_NET_WM_WINDOW_TYPE_NORMAL" "_NET_WM_STATE_HIDDEN"
384 ;; "_NET_WM_STATE_FULLSCREEN"
385 ;; "_NET_WM_STATE_ABOVE"
386 ;; "_NET_WM_STATE_BELOW"
387 ;; "_NET_WM_STATE_DEMANDS_ATTENTION"
389 ;; "_NET_WM_ALLOWED_ACTIONS"
390 ;; "_NET_WM_ACTION_MOVE"
391 ;; "_NET_WM_ACTION_RESIZE"
392 ;; "_NET_WM_ACTION_SHADE"
393 ;; "_NET_WM_ACTION_STICK"
394 ;; "_NET_WM_ACTION_MAXIMIZE_HORZ"
395 ;; "_NET_WM_ACTION_MAXIMIZE_VERT"
396 ;; "_NET_WM_ACTION_FULLSCREEN"
397 ;; "_NET_WM_ACTION_CHANGE_DESKTOP"
398 ;; "_NET_WM_ACTION_CLOSE"
400 ;; ))
403 ;;(defun intern-atoms (display)
404 ;; (declare (type xlib:display display))
405 ;; (mapcar #'(lambda (atom-name) (xlib:intern-atom display atom-name))
406 ;; +exwm-atoms+)
407 ;; (values))
411 ;;(defun get-atoms-property (window property-atom atom-list-p)
412 ;; "Returns a list of atom-name (if atom-list-p is t) otherwise returns
413 ;; a list of atom-id."
414 ;; (xlib:get-property window property-atom
415 ;; :transform (when atom-list-p
416 ;; (lambda (id)
417 ;; (xlib:atom-name (xlib:drawable-display window) id)))))
419 ;;(defun set-atoms-property (window atoms property-atom &key (mode :replace))
420 ;; "Sets the property designates by `property-atom'. ATOMS is a list of atom-id
421 ;; or a list of keyword atom-names."
422 ;; (xlib:change-property window property-atom atoms :ATOM 32
423 ;; :mode mode
424 ;; :transform (unless (integerp (car atoms))
425 ;; (lambda (atom-key)
426 ;; (xlib:find-atom (xlib:drawable-display window) atom-key)))))
431 ;;(defun net-wm-state (window)
432 ;; (get-atoms-property window :_NET_WM_STATE t))
434 ;;(defsetf net-wm-state (window &key (mode :replace)) (states)
435 ;; `(set-atoms-property ,window ,states :_NET_WM_STATE :mode ,mode))
438 (defun hide-window (window)
439 (when window
440 (setf (window-state window) +iconic-state+
441 (xlib:window-event-mask window) (remove :structure-notify *window-events*))
442 (xlib:unmap-window window)
443 (setf (xlib:window-event-mask window) *window-events*)))
447 (defun window-type (window)
448 "Return one of :desktop, :dock, :toolbar, :utility, :splash,
449 :dialog, :transient, :maxsize and :normal."
450 (or (and (let ((hints (xlib:wm-normal-hints window)))
451 (and hints (or (xlib:wm-size-hints-max-width hints)
452 (xlib:wm-size-hints-max-height hints)
453 (xlib:wm-size-hints-min-aspect hints)
454 (xlib:wm-size-hints-max-aspect hints))))
455 :maxsize)
456 (let ((net-wm-window-type (xlib:get-property window :_NET_WM_WINDOW_TYPE)))
457 (when net-wm-window-type
458 (dolist (type-atom net-wm-window-type)
459 (when (assoc (xlib:atom-name *display* type-atom) +netwm-window-types+)
460 (return (cdr (assoc (xlib:atom-name *display* type-atom) +netwm-window-types+)))))))
461 (and (xlib:get-property window :WM_TRANSIENT_FOR)
462 :transient)
463 :normal))
468 ;;; Stolen from Eclipse
469 (defun send-configuration-notify (window x y w h bw)
470 "Send a synthetic configure notify event to the given window (ICCCM 4.1.5)"
471 (xlib:send-event window :configure-notify (xlib:make-event-mask :structure-notify)
472 :event-window window
473 :window window
474 :x x :y y
475 :width w
476 :height h
477 :border-width bw
478 :propagate-p nil))
482 (defun send-client-message (window type &rest data)
483 "Send a client message to a client's window."
484 (xlib:send-event window
485 :client-message nil
486 :window window
487 :type type
488 :format 32
489 :data data))
495 (defun raise-window (window)
496 "Map the window if needed and bring it to the top of the stack. Does not affect focus."
497 (when (xlib:window-p window)
498 (when (window-hidden-p window)
499 (unhide-window window))
500 (setf (xlib:window-priority window) :above)))
502 (defun focus-window (window)
503 "Give the window focus."
504 (when (xlib:window-p window)
505 (xlib:set-input-focus *display* window :parent)))
507 (defun raise-and-focus-window (window)
508 "Raise and focus."
509 (raise-window window)
510 (focus-window window))
512 (defun no-focus ()
513 "don't focus any window but still read keyboard events."
514 (xlib:set-input-focus *display* *no-focus-window* :pointer-root))
517 (defun lower-window (window sibling)
518 "Map the window if needed and bring it just above sibling. Does not affect focus."
519 (when (xlib:window-p window)
520 (when (window-hidden-p window)
521 (unhide-window window))
522 (setf (xlib:window-priority window sibling) :below)))
527 (let ((cursor-font nil)
528 (cursor nil)
529 (pointer-grabbed nil))
530 (defun free-grab-pointer ()
531 (when cursor
532 (xlib:free-cursor cursor)
533 (setf cursor nil))
534 (when cursor-font
535 (xlib:close-font cursor-font)
536 (setf cursor-font nil)))
538 (defun xgrab-init-pointer ()
539 (setf pointer-grabbed nil))
541 (defun xgrab-pointer-p ()
542 pointer-grabbed)
544 (defun xgrab-pointer (root cursor-char cursor-mask-char
545 &optional (pointer-mask '(:enter-window :pointer-motion
546 :button-press :button-release)) owner-p)
547 "Grab the pointer and set the pointer shape."
548 (when pointer-grabbed
549 (xungrab-pointer))
550 (setf pointer-grabbed t)
551 (let* ((white (xlib:make-color :red 1.0 :green 1.0 :blue 1.0))
552 (black (xlib:make-color :red 0.0 :green 0.0 :blue 0.0)))
553 (cond (cursor-char
554 (setf cursor-font (xlib:open-font *display* "cursor")
555 cursor (xlib:create-glyph-cursor :source-font cursor-font
556 :source-char (or cursor-char 68)
557 :mask-font cursor-font
558 :mask-char (or cursor-mask-char 69)
559 :foreground black
560 :background white))
561 (xlib:grab-pointer root pointer-mask
562 :owner-p owner-p :sync-keyboard-p nil :sync-pointer-p nil :cursor cursor))
564 (xlib:grab-pointer root pointer-mask
565 :owner-p owner-p :sync-keyboard-p nil :sync-pointer-p nil)))))
567 (defun xungrab-pointer ()
568 "Remove the grab on the cursor and restore the cursor shape."
569 (setf pointer-grabbed nil)
570 (xlib:ungrab-pointer *display*)
571 (xlib:display-finish-output *display*)
572 (free-grab-pointer)))
575 (let ((keyboard-grabbed nil))
576 (defun xgrab-init-keyboard ()
577 (setf keyboard-grabbed nil))
579 (defun xgrab-keyboard-p ()
580 keyboard-grabbed)
582 (defun xgrab-keyboard (root)
583 (setf keyboard-grabbed t)
584 (xlib:grab-keyboard root :owner-p nil :sync-keyboard-p nil :sync-pointer-p nil))
587 (defun xungrab-keyboard ()
588 (setf keyboard-grabbed nil)
589 (xlib:ungrab-keyboard *display*)))
596 (defun ungrab-all-buttons (window)
597 (xlib:ungrab-button window :any :modifiers :any))
599 (defun grab-all-buttons (window)
600 (ungrab-all-buttons window)
601 (xlib:grab-button window :any '(:button-press :button-release :pointer-motion)
602 :modifiers :any
603 :owner-p nil
604 :sync-pointer-p t
605 :sync-keyboard-p nil))
607 (defun ungrab-all-keys (window)
608 (xlib:ungrab-key window :any :modifiers :any))
611 (defun stop-button-event ()
612 (xlib:allow-events *display* :sync-pointer))
614 (defun replay-button-event ()
615 (xlib:allow-events *display* :replay-pointer))
625 ;;; Mouse action on window
626 (let (add-fn add-arg dx dy window)
627 (define-handler move-window-mode :motion-notify (root-x root-y)
628 (unless (compress-motion-notify)
629 (if add-fn
630 (multiple-value-bind (move-x move-y)
631 (apply add-fn add-arg)
632 (when move-x
633 (setf (x-drawable-x window) (+ root-x dx)))
634 (when move-y
635 (setf (x-drawable-y window) (+ root-y dy))))
636 (setf (x-drawable-x window) (+ root-x dx)
637 (x-drawable-y window) (+ root-y dy)))))
639 (define-handler move-window-mode :key-release ()
640 (throw 'exit-move-window-mode nil))
642 (define-handler move-window-mode :button-release ()
643 (throw 'exit-move-window-mode nil))
645 (defun move-window (orig-window orig-x orig-y &optional additional-fn additional-arg)
646 (setf window orig-window
647 add-fn additional-fn
648 add-arg additional-arg
649 dx (- (x-drawable-x window) orig-x)
650 dy (- (x-drawable-y window) orig-y)
651 (xlib:window-border window) (get-color *color-move-window*))
652 (raise-window window)
653 (let ((pointer-grabbed-p (xgrab-pointer-p)))
654 (unless pointer-grabbed-p
655 (xgrab-pointer *root* nil nil))
656 (when additional-fn
657 (apply additional-fn additional-arg))
658 (generic-mode 'move-window-mode 'exit-move-window-mode
659 :original-mode '(main-mode))
660 (unless pointer-grabbed-p
661 (xungrab-pointer)))))
664 (let (add-fn add-arg window
665 o-x o-y
666 orig-width orig-height
667 min-width max-width
668 min-height max-height)
669 (define-handler resize-window-mode :motion-notify (root-x root-y)
670 (unless (compress-motion-notify)
671 (if add-fn
672 (multiple-value-bind (resize-w resize-h)
673 (apply add-fn add-arg)
674 (when resize-w
675 (setf (x-drawable-width window) (min (max (+ orig-width (- root-x o-x)) 10 min-width) max-width)))
676 (when resize-h
677 (setf (x-drawable-height window) (min (max (+ orig-height (- root-y o-y)) 10 min-height) max-height))))
678 (setf (x-drawable-width window) (min (max (+ orig-width (- root-x o-x)) 10 min-width) max-width)
679 (x-drawable-height window) (min (max (+ orig-height (- root-y o-y)) 10 min-height) max-height)))))
681 (define-handler resize-window-mode :key-release ()
682 (throw 'exit-resize-window-mode nil))
684 (define-handler resize-window-mode :button-release ()
685 (throw 'exit-resize-window-mode nil))
687 (defun resize-window (orig-window orig-x orig-y &optional additional-fn additional-arg)
688 (let* ((pointer-grabbed-p (xgrab-pointer-p))
689 (hints (xlib:wm-normal-hints orig-window)))
690 (setf window orig-window
691 add-fn additional-fn
692 add-arg additional-arg
693 o-x orig-x
694 o-y orig-y
695 orig-width (x-drawable-width window)
696 orig-height (x-drawable-height window)
697 min-width (or (and hints (xlib:wm-size-hints-min-width hints)) 0)
698 min-height (or (and hints (xlib:wm-size-hints-min-height hints)) 0)
699 max-width (or (and hints (xlib:wm-size-hints-max-width hints)) most-positive-fixnum)
700 max-height (or (and hints (xlib:wm-size-hints-max-height hints)) most-positive-fixnum)
701 (xlib:window-border window) (get-color *color-move-window*))
702 (raise-window window)
703 (unless pointer-grabbed-p
704 (xgrab-pointer *root* nil nil))
705 (when additional-fn
706 (apply additional-fn additional-arg))
707 (generic-mode 'resize-window-mode 'exit-resize-window-mode
708 :original-mode '(main-mode))
709 (unless pointer-grabbed-p
710 (xungrab-pointer)))))
713 (define-handler wait-mouse-button-release-mode :button-release ()
714 (throw 'exit-wait-mouse-button-release-mode nil))
716 (defun wait-mouse-button-release (&optional cursor-char cursor-mask-char)
717 (let ((pointer-grabbed-p (xgrab-pointer-p)))
718 (unless pointer-grabbed-p
719 (xgrab-pointer *root* cursor-char cursor-mask-char))
720 (generic-mode 'wait-mouse-button-release 'exit-wait-mouse-button-release-mode)
721 (unless pointer-grabbed-p
722 (xungrab-pointer))))
727 (let ((color-hash (make-hash-table :test 'equal)))
728 (defun get-color (color)
729 (multiple-value-bind (val foundp)
730 (gethash color color-hash)
731 (if foundp
733 (setf (gethash color color-hash)
734 (xlib:alloc-color (xlib:screen-default-colormap *screen*) color))))))
738 (defgeneric ->color (color))
740 (defmethod ->color ((color-name string))
741 color-name)
743 (defmethod ->color ((color integer))
744 (labels ((hex->float (color)
745 (/ (logand color #xFF) 256.0)))
746 (xlib:make-color :blue (hex->float color)
747 :green (hex->float (ash color -8))
748 :red (hex->float (ash color -16)))))
750 (defmethod ->color ((color list))
751 (destructuring-bind (red green blue) color
752 (xlib:make-color :blue red :green green :red blue)))
754 (defmethod ->color ((color xlib:color))
755 color)
757 (defmethod ->color (color)
758 (format t "Wrong color type: ~A~%" color)
759 "White")
762 (defun color->rgb (color)
763 (multiple-value-bind (r g b)
764 (xlib:color-rgb color)
765 (+ (ash (round (* 256 r)) +16)
766 (ash (round (* 256 g)) +8)
767 (round (* 256 b)))))
773 (defmacro my-character->keysyms (ch)
774 "Convert a char to a keysym"
775 ;; XLIB:CHARACTER->KEYSYMS should probably be implemented in NEW-CLX
776 ;; some day. Or just copied from MIT-CLX or some other CLX
777 ;; implementation (see translate.lisp and keysyms.lisp). For now,
778 ;; we do like this. It suffices for modifiers and ASCII symbols.
779 (if (fboundp 'xlib:character->keysyms)
780 `(xlib:character->keysyms ,ch)
781 `(list
782 (case ,ch
783 (:character-set-switch #xFF7E)
784 (:left-shift #xFFE1)
785 (:right-shift #xFFE2)
786 (:left-control #xFFE3)
787 (:right-control #xFFE4)
788 (:caps-lock #xFFE5)
789 (:shift-lock #xFFE6)
790 (:left-meta #xFFE7)
791 (:right-meta #xFFE8)
792 (:left-alt #xFFE9)
793 (:right-alt #xFFEA)
794 (:left-super #xFFEB)
795 (:right-super #xFFEC)
796 (:left-hyper #xFFED)
797 (:right-hyper #xFFEE)
799 (etypecase ,ch
800 (character
801 ;; Latin-1 characters have their own value as keysym
802 (if (< 31 (char-code ,ch) 256)
803 (char-code ,ch)
804 (error "Don't know how to get keysym from ~A" ,ch)))))))))
809 (defun char->keycode (char)
810 "Convert a character to a keycode"
811 (xlib:keysym->keycodes *display* (first (my-character->keysyms char))))
814 (defun keycode->char (code state)
815 (xlib:keysym->character *display* (xlib:keycode->keysym *display* code 0) state))
817 (defun modifiers->state (modifier-list)
818 (apply #'xlib:make-state-mask modifier-list))
820 (defun state->modifiers (state)
821 (xlib:make-state-keys state))
823 (defun keycode->keysym (code modifiers)
824 (xlib:keycode->keysym *display* code (cond ((member :shift modifiers) 1)
825 ((member :mod-5 modifiers) 4)
826 (t 0))))
829 (defmacro with-grab-keyboard-and-pointer ((cursor mask old-cursor old-mask) &body body)
830 `(let ((pointer-grabbed (xgrab-pointer-p))
831 (keyboard-grabbed (xgrab-keyboard-p)))
832 (xgrab-pointer *root* ,cursor ,mask)
833 (unless keyboard-grabbed
834 (xgrab-keyboard *root*))
835 (unwind-protect
836 (progn
837 ,@body)
838 (if pointer-grabbed
839 (xgrab-pointer *root* ,old-cursor ,old-mask)
840 (xungrab-pointer))
841 (unless keyboard-grabbed
842 (xungrab-keyboard)))))
849 (let ((modifier-list nil))
850 (defun init-modifier-list ()
851 (dolist (name '("Shift_L" "Shift_R" "Control_L" "Control_R"
852 "Alt_L" "Alt_R" "Meta_L" "Meta_R" "Hyper_L" "Hyper_R"
853 "Mode_switch" "script_switch" "ISO_Level3_Shift"
854 "Caps_Lock" "Scroll_Lock" "Num_Lock"))
855 (awhen (xlib:keysym->keycodes *display* (keysym-name->keysym name))
856 (push it modifier-list))))
858 (defun modifier-p (code)
859 (member code modifier-list)))
861 (defun wait-no-key-or-button-press ()
862 (with-grab-keyboard-and-pointer (66 67 66 67)
863 (loop
864 (let ((key (loop for k across (xlib:query-keymap *display*)
865 for code from 0
866 when (and (plusp k) (not (modifier-p code)))
867 return t))
868 (button (loop for b in (xlib:make-state-keys (nth-value 4 (xlib:query-pointer *root*)))
869 when (member b '(:button-1 :button-2 :button-3 :button-4 :button-5))
870 return t)))
871 (when (and (not key) (not button))
872 (loop while (xlib:event-case (*display* :discard-p t :peek-p nil :timeout 0)
873 (:motion-notify () t)
874 (:key-press () t)
875 (:key-release () t)
876 (:button-press () t)
877 (:button-release () t)
878 (t nil)))
879 (return))))))
882 (defun wait-a-key-or-button-press ()
883 (with-grab-keyboard-and-pointer (24 25 66 67)
884 (loop
885 (let ((key (loop for k across (xlib:query-keymap *display*)
886 unless (zerop k) return t))
887 (button (loop for b in (xlib:make-state-keys (nth-value 4 (xlib:query-pointer *root*)))
888 when (member b '(:button-1 :button-2 :button-3 :button-4 :button-5))
889 return t)))
890 (when (or key button)
891 (return))))))
895 (defun compress-motion-notify ()
896 (when *have-to-compress-notify*
897 (loop while (xlib:event-cond (*display* :timeout 0)
898 (:motion-notify () t)))))
901 (defun display-all-cursors (&optional (display-time 1))
902 "Display all X11 cursors for display-time seconds"
903 (loop for i from 0 to 152 by 2
904 do (xgrab-pointer *root* i (1+ i))
905 (dbg i)
906 (sleep display-time)
907 (xungrab-pointer)))
911 ;;; Double buffering tools
912 (defun clear-pixmap-buffer (window gc)
913 (if (equal *transparent-background* :pseudo)
914 (xlib:copy-area *background-image* *background-gc*
915 (x-drawable-x window) (x-drawable-y window)
916 (x-drawable-width window) (x-drawable-height window)
917 *pixmap-buffer* 0 0)
918 (xlib:with-gcontext (gc :foreground (xlib:gcontext-background gc)
919 :background (xlib:gcontext-foreground gc))
920 (xlib:draw-rectangle *pixmap-buffer* gc
921 0 0 (x-drawable-width window) (x-drawable-height window)
922 t))))
925 (defun copy-pixmap-buffer (window gc)
926 (xlib:copy-area *pixmap-buffer* gc
927 0 0 (x-drawable-width window) (x-drawable-height window)
928 window 0 0))
932 (defun is-a-key-pressed-p ()
933 (loop for k across (xlib:query-keymap *display*)
934 when (plusp k)
935 return t))
937 ;;; Windows wm class and name tests
938 (defmacro defun-equal-wm-class (symbol class)
939 `(defun ,symbol (window)
940 (when (xlib:window-p window)
941 (string-equal (xlib:get-wm-class window) ,class))))
943 (defmacro defun-equal-wm-name (symbol name)
944 `(defun ,symbol (window)
945 (when (xlib:window-p window)
946 (string-equal (xlib:wm-name window) ,name))))