summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--color.lisp2
-rw-r--r--core.lisp3369
-rw-r--r--events.lisp604
-rw-r--r--fdump.lisp2
-rw-r--r--frame.lisp682
-rw-r--r--group.lisp231
-rw-r--r--input.lisp2
-rw-r--r--keysyms.lisp2
-rw-r--r--keytrans.lisp2
-rw-r--r--kmap.lisp32
-rw-r--r--manual.lisp2
-rw-r--r--message-window.lisp248
-rw-r--r--mode-line.lisp2
-rw-r--r--package.lisp2
-rw-r--r--primitives.lisp8
-rw-r--r--screen.lisp616
-rw-r--r--selection.lisp88
-rw-r--r--stumpwm.asd7
-rw-r--r--stumpwm.lisp2
-rw-r--r--user.lisp5
-rw-r--r--window.lisp1039
21 files changed, 3559 insertions, 3388 deletions
diff --git a/color.lisp b/color.lisp
index 700ab3b..aeff36e 100644
--- a/color.lisp
+++ b/color.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2007 Jonathan Moore Liles
+;; Copyright (C) 2007-2008 Jonathan Moore Liles
;;
;; This file is part of stumpwm.
;;
diff --git a/core.lisp b/core.lisp
index c7202af..6c70a7f 100644
--- a/core.lisp
+++ b/core.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
@@ -26,41 +26,6 @@
(in-package :stumpwm)
-(export '(*top-map*
- current-group
- current-screen
- current-srceen
- current-window
- def-window-attr
- echo-string
- err
- get-x-selection
- message
- save-frame-excursion
- screen-current-window
- set-normal-gravity
- set-maxsize-gravity
- set-transient-gravity
- set-window-geometry
- set-fg-color
- set-bg-color
- set-border-color
- set-win-bg-color
- set-focus-color
- set-unfocus-color
- set-msg-border-width
- set-frame-outline-width
- set-font
- set-x-selection
- window-send-string))
-
-;; Do it this way so its easier to wipe the map and get a clean one.
-(when (null *top-map*)
- (setf *top-map*
- (let ((m (make-sparse-keymap)))
- (define-key m (kbd "C-t") '*root-map*)
- m)))
-
;; Wow, is there an easier way to do this?
(defmacro def-thing-attr-macro (thing hash-slot)
(let ((attr (gensym "ATTR"))
@@ -76,2649 +41,6 @@
(defun (setf ,(intern (format nil ,(format nil "~a-~~a" thing) ,attr))) (,,val ,,obj)
(setf (gethash ,,attr (,(quote ,hash-slot) ,,obj))) ,,val))))))
-;; Screen helper functions
-
-(defun translate-id (src src-start src-end font dst dst-start)
- "A simple replacement for xlib:translate-default. just the
-identity with a range check."
- (let ((min (xlib:font-min-char font))
- (max (xlib:font-max-char font)))
- (decf src-end)
- (if (stringp src) ; clx does this test so i guess it's needed
- (loop for i from src-start to src-end
- for j from dst-start
- as c = (char-code (char src i))
- if (<= min c max) do (setf (aref dst j) c)
- ;; replace unknown characters with question marks
- else do (setf (aref dst j) (char-code #\?))
- finally (return i))
- (loop for i from src-start to src-end
- for j from dst-start
- as c = (elt src i)
- as n = (if (characterp c) (char-code c) c)
- if (and (integerp n) (<= min n max)) do (setf (aref dst j) n)
- ;; ditto
- else do (setf (aref dst j) (char-code #\?))
- finally (return i)))))
-
-(defun screen-x (screen)
- (declare (ignore screen))
- 0)
-
-(defun screen-y (screen)
- (declare (ignore screen))
- 0)
-
-(defun screen-height (screen)
- (let ((root (screen-root screen)))
- (xlib:drawable-height root)))
-
-(defun screen-true-height (screen)
- "Return the height of the screen regardless of the modeline"
- (let ((root (screen-root screen)))
- (xlib:drawable-height root)))
-
-(defun screen-width (screen)
- (let ((root (screen-root screen)))
- (xlib:drawable-width root)))
-
-(defun find-screen (root)
- "Return the screen containing the root window."
- (find-if (lambda (s)
- (xlib:window-equal (screen-root s) root))
- *screen-list*))
-
-(defun screen-windows (screen)
- (mapcan (lambda (g) (copy-list (group-windows g))) (screen-groups screen)))
-
-
-;;; Group function
-
-(defun current-group (&optional (screen (current-screen)))
- "Return the current group for the current screen, unless
-otherwise specified."
- (screen-current-group screen))
-
-(defun move-group-to-head (screen group)
- "Move window to the head of the group's window list."
- ;(assert (member window (screen-mapped-windows screen)))
- (setf (screen-groups screen) (delete group (screen-groups screen)))
- (push group (screen-groups screen)))
-
-(defun sort-groups (screen)
- "Return a copy of the screen's group list sorted by number."
- (sort1 (screen-groups screen) '< :key 'group-number))
-
-(defun fmt-group-status (group)
- (let ((screen (group-screen group)))
- (cond ((eq group (screen-current-group screen))
- #\*)
- ((and (typep (second (screen-groups screen)) 'group)
- (eq group (second (screen-groups screen))))
- #\+)
- (t #\-))))
-
-(defun find-free-group-number (screen)
- "Return a free group number in SCREEN."
- (find-free-number (mapcar 'group-number (screen-groups screen)) 1))
-
-(defun find-free-hidden-group-number (screen)
- "Return a free hidden group number for SCREEN. Hidden group numbers
-start at -1 and go down."
- (find-free-number (mapcar 'group-number (screen-groups screen)) -1 :negative))
-
-(defun group-current-window (group)
- (frame-window (tile-group-current-frame group)))
-
-(defun non-hidden-groups (groups)
- "Return only those groups that are not hidden."
- (remove-if (lambda (g)
- (< (group-number g) 1))
- groups))
-
-(defun netwm-group-id (group)
- "netwm specifies that desktop/group numbers are contiguous and start
-at 0. Return a netwm compliant group id."
- (let ((screen (group-screen group)))
- (position group (sort-groups screen))))
-
-(defun switch-to-group (new-group)
- (let* ((screen (group-screen new-group))
- (old-group (screen-current-group screen)))
- (unless (eq new-group old-group)
- ;; restore the visible windows
- (dolist (w (group-windows new-group))
- (when (eq (window-state w) +normal-state+)
- (xwin-unhide (window-xwin w) (window-parent w))))
- (dolist (w (reverse (group-windows old-group)))
- (when (eq (window-state w) +normal-state+)
- (xwin-hide w)))
- (setf (screen-current-group screen) new-group)
- (move-group-to-head screen new-group)
- ;; restore the focus
- (setf (screen-focus screen) nil)
- (focus-frame new-group (tile-group-current-frame new-group))
- (xlib:change-property (screen-root screen) :_NET_CURRENT_DESKTOP
- (list (netwm-group-id new-group))
- :cardinal 32)
- (run-hook-with-args *focus-group-hook* new-group old-group)))
- (show-frame-indicator new-group))
-
-(defun move-window-to-group (window to-group)
- (labels ((really-move-window (window to-group)
- (unless (eq (window-group window) to-group)
- (let ((old-group (window-group window))
- (old-frame (window-frame window)))
- (hide-window window)
- ;; house keeping
- (setf (group-windows (window-group window))
- (remove window (group-windows (window-group window))))
- (setf (window-frame window) (tile-group-current-frame to-group)
- (window-group window) to-group
- (window-number window) (find-free-window-number to-group))
- ;; try to put the window in the appropriate frame for the group
- (multiple-value-bind (placed-group frame raise) (get-window-placement (window-screen window) window)
- (declare (ignore placed-group))
- (when frame
- (setf (window-frame window) frame)
- (when raise
- (setf (tile-group-current-frame to-group) frame
- (frame-window frame) nil))))
- (push window (group-windows to-group))
- (sync-frame-windows to-group (tile-group-current-frame to-group))
- ;; maybe pick a new window for the old frame
- (when (eq (frame-window old-frame) window)
- (setf (frame-window old-frame) (first (frame-windows old-group old-frame)))
- (focus-frame old-group old-frame))
- ;; maybe show the window in its new frame
- (when (null (frame-window (window-frame window)))
- (frame-raise-window (window-group window) (window-frame window) window))
- (xlib:change-property (window-xwin window) :_NET_WM_DESKTOP
- (list (netwm-group-id to-group))
- :cardinal 32)))))
- ;; When a modal window is moved, all the windows it shadows must be moved
- ;; as well. When a shadowed window is moved, the modal shadowing it must
- ;; be moved.
- (cond
- ((window-modal-p window)
- (mapc (lambda (w)
- (really-move-window w to-group))
- (append (list window) (shadows-of window))))
- ((modals-of window)
- (mapc (lambda (w)
- (move-window-to-group w to-group))
- (modals-of window)))
- (t
- (really-move-window window to-group)))))
-
-(defun next-group (current &optional (list (screen-groups (group-screen current))))
- ;; ditch the negative groups
- (setf list (non-hidden-groups list))
- (let* ((matches (member current list)))
- (if (null (cdr matches))
- ;; If the last one in the list is current, then
- ;; use the first one.
- (car list)
- ;; Otherwise, use the next one in the list.
- (cadr matches))))
-
-(defun merge-groups (from-group to-group)
- "Merge all windows in FROM-GROUP into TO-GROUP."
- (dolist (window (group-windows from-group))
- (move-window-to-group window to-group)))
-
-(defun netwm-update-groups (screen)
- "update all windows to reflect a change in the group list."
- ;; FIXME: This could be optimized only to update windows when there
- ;; is a need.
- (loop for i from 0
- for group in (sort-groups screen)
- do (dolist (w (group-windows group))
- (xlib:change-property (window-xwin w) :_NET_WM_DESKTOP
- (list i)
- :cardinal 32))))
-
-(defun kill-group (group to-group)
- (when (> (length (screen-groups (group-screen group))) 1)
- (let ((screen (group-screen group)))
- (merge-groups group to-group)
- (setf (screen-groups screen) (remove group (screen-groups screen)))
- (netwm-update-groups screen))))
-
-(defun netwm-set-group-properties (screen)
- "Set NETWM properties regarding groups of SCREEN.
-Groups are known as \"virtual desktops\" in the NETWM standard."
- (let ((root (screen-root screen)))
- ;; _NET_NUMBER_OF_DESKTOPS
- (xlib:change-property root :_NET_NUMBER_OF_DESKTOPS
- (list (length (screen-groups screen)))
- :cardinal 32)
- (unless *initializing*
- ;; _NET_CURRENT_DESKTOP
- (xlib:change-property root :_NET_CURRENT_DESKTOP
- (list (netwm-group-id (screen-current-group screen)))
- :cardinal 32))
- ;; _NET_DESKTOP_NAMES
- (xlib:change-property root :_NET_DESKTOP_NAMES
- (let ((names (mapcan
- (lambda (group)
- (list (string-to-utf8 (group-name group))
- '(0)))
- (sort-groups screen))))
- (apply #'concatenate 'list names))
- :UTF8_STRING 8)))
-
-(defun add-group (screen name)
- "Create a new group in SCREEN with the supplied name. group names
- starting with a . are considered hidden groups. Hidden groups are
- skipped by gprev and gnext and do not show up in the group
- listings (unless *list-hidden-groups* is T). They also use negative
- numbers."
- (check-type screen screen)
- (check-type name string)
- (unless (or (string= name "")
- (string= name "."))
- (or (find-group screen name)
- (let* ((heads (copy-heads screen))
- (ng (make-tile-group
- :frame-tree heads
- :current-frame (first heads)
- :screen screen
- :number (if (char= (char name 0) #\.)
- (find-free-hidden-group-number screen)
- (find-free-group-number screen))
- :name name)))
- (setf (screen-groups screen) (append (screen-groups screen) (list ng)))
- (netwm-set-group-properties screen)
- (netwm-update-groups screen)
- ng))))
-
-(defun find-group (screen name)
- "Return the group with the name, NAME. Or NIL if none exists."
- (find name (screen-groups screen) :key 'group-name :test 'string=))
-
-
-;;; Window functions
-
-
-;; Since StumpWM already uses the term 'group' to refer to Virtual Desktops,
-;; we'll call the grouped windows of an application a 'gang'
-
-;; maybe follow transient_for to find leader.
-(defun window-leader (window)
- (when window
- (or (first (window-property window :WM_CLIENT_LEADER))
- (let ((id (window-transient-for window)))
- (when id
- (window-leader (window-by-id id)))))))
-
-;; A modal dialog can either shadow a single window, or all windows
-;; in its gang, depending on the value of WM_TRANSIENT_FOR
-
-;; If a window is shadowed by a modal dialog, so are any other
-;; transients belonging to that window.
-
-(defun window-transient-for (window)
- (first (window-property window :WM_TRANSIENT_FOR)))
-
-(defun window-modal-p (window)
- (find-wm-state (window-xwin window) :_NET_WM_STATE_MODAL))
-
-(defun window-transient-p (window)
- (find (window-type window) '(:transient :dialog)))
-
-;; FIXME: use WM_HINTS.group_leader
-(defun window-gang (window)
- "Return a list of other windows in WINDOW's gang."
- (let ((leader (window-leader window))
- (screen (window-screen window)))
- (when leader
- (loop for w in (screen-windows screen)
- as l = (window-leader w)
- if (and (not (eq w window)) l (= leader l))
- collect w))))
-
-(defun only-modals (windows)
- "Out of WINDOWS, return a list of those which are modal."
- (remove-if-not 'window-modal-p (copy-list windows)))
-
-(defun x-of (window filter)
- (let* ((root (screen-root (window-screen window)))
- (root-id (xlib:drawable-id root))
- (win-id (xlib:window-id (window-xwin window))))
- (loop for w in (funcall filter (window-gang window))
- as tr = (window-transient-for w)
- when (or (not tr) ; modal for group
- (eq tr root-id) ; ditto
- (eq tr win-id)) ; modal for win
- collect w)))
-
-
-;; The modals of a transient are the modals of the window
-;; the transient belongs to.
-(defun modals-of (window)
- "Given WINDOW return the modal dialogs which are shadowing it, if any."
- (loop for m in (only-modals (window-gang window))
- when (find window (shadows-of m))
- collect m))
-
-(defun transients-of (window)
- "Return the transient dialogs belonging to WINDOW"
- (x-of window 'only-transients))
-
-(defun shadows-of (window)
- "Given modal window WINDOW return the list of windows in its shadow."
- (let* ((root (screen-root (window-screen window)))
- (root-id (xlib:drawable-id root))
- (tr (window-transient-for window)))
- (cond
- ((or (not tr)
- (eq tr root-id))
- (window-gang window))
- (t
- (let ((w (window-by-id tr)))
- (append (list w) (transients-of w)))))))
-
-(defun only-transients (windows)
- "Out of WINDOWS, return a list of those which are transient."
- (remove-if-not 'window-transient-p (copy-list windows)))
-
-(defun really-raise-window (window)
- (frame-raise-window (window-group window) (window-frame window) window))
-
-(defun raise-modals-of (window)
- (mapc 'really-raise-window (modals-of window)))
-
-(defun raise-modals-of-gang (window)
- (mapc 'really-raise-window (only-modals (window-gang window))))
-
-(defun raise-transients-of-gang (window)
- (mapc 'really-raise-window (only-transients (window-gang window))))
-
-(defun all-windows ()
- (mapcan (lambda (s) (copy-list (screen-windows s))) *screen-list*))
-
-(defun visible-windows ()
- "Return a list of visible windows (on all screens)"
- (loop for s in *screen-list*
- nconc (delete-if 'window-hidden-p (copy-list (group-windows (screen-current-group s))))))
-
-(defun top-windows ()
- "Return a list of windows on top (on all screen)"
- (loop for s in *screen-list*
- nconc (mapcar 'frame-window (group-frames (screen-current-group s)))))
-
-(defun window-name (window)
- (or (window-user-title window)
- (case *window-name-source*
- (:resource-name (window-res window))
- (:class (window-class window))
- (t (window-title window)))))
-
-(defun window-id (window)
- (xlib:window-id (window-xwin window)))
-
-(defun window-in-current-group-p (window)
- (eq (window-group window)
- (screen-current-group (window-screen window))))
-
-(defun window-screen (window)
- (group-screen (window-group window)))
-
-(defun update-window-border (window)
- ;; give it a colored border but only if there are more than 1 frames.
- (let* ((group (window-group window))
- (screen (group-screen group)))
- (let ((c (if (and (> (length (group-frames group)) 1)
- (eq (group-current-window group) window))
- (screen-focus-color screen)
- (screen-unfocus-color screen))))
- (setf (xlib:window-border (window-parent window)) c
- ;; windows that dont fill the entire screen have a transparent background.
- (xlib:window-background (window-parent window))
- (if (eq (window-type window) :normal)
- (if (eq *window-border-style* :thick)
- c
- (screen-unfocus-color screen))
- :none))
- ;; get the background updated
- (xlib:clear-area (window-parent window)))))
-
-(defun send-client-message (window type &rest data)
- "Send a client message to a client's window."
- (xlib:send-event (window-xwin window)
- :client-message nil
- :window (window-xwin window)
- :type type
- :format 32
- :data data))
-
-(defun fmt-window-status (window)
- (let ((group (window-group window)))
- (cond ((eq window (group-current-window group))
- #\*)
- ((and (typep (second (group-windows group)) 'window)
- (eq window (second (group-windows group))))
- #\+)
- (t #\-))))
-
-(defun fmt-window-marked (window)
- (if (window-marked window)
- #\#
- #\Space))
-
-;; (defun update-window-mark (window)
-;; "Called when we need to draw or clear the mark."
-;; ;; FIXME: This doesn't work at all. I'd like to have little squares
-;; ;; that look like clamps on the corners of the window, likes its
-;; ;; sorta grabbed. But i dunno how to properly draw them.
-;; (let* ((screen (window-screen window)))
-;; (if (window-marked window)
-;; (xlib:draw-rectangle (window-parent window) (screen-marked-gc (window-screen window))
-;; 0 0 300 200 t)
-;; (xlib:clear-area (window-parent window)))))
-
-(defun xwin-net-wm-name (win)
- "Return the netwm wm name"
- (let ((name (xlib:get-property win :_NET_WM_NAME)))
- (when name
- (utf8-to-string name))))
-
-(defun xwin-name (win)
- (or
- (xwin-net-wm-name win)
- (xlib:wm-name win)))
-
-;; FIXME: should we raise the winodw or its parent?
-(defun raise-window (win)
- "Map the window if needed and bring it to the top of the stack. Does not affect focus."
- (when (window-hidden-p win)
- (unhide-window win)
- (update-configuration win))
- (when (window-in-current-group-p win)
- (setf (xlib:window-priority (window-parent win)) :top-if)))
-
-;; some handy wrappers
-
-(defun true-height (win)
- (xlib:with-state (win)
- (+ (xlib:drawable-height win) (* (xlib:drawable-border-width win) 2))))
-
-(defun true-width (win)
- (xlib:with-state (win)
- (+ (xlib:drawable-width win) (* (xlib:drawable-border-width win) 2))))
-
-(defun xwin-border-width (win)
- (xlib:drawable-border-width win))
-
-(defun (setf xwin-border-width) (width win)
- (setf (xlib:drawable-border-width win) width))
-
-(defun default-border-width-for-type (type)
- (ecase type
- (:dock 0)
- (:normal *normal-border-width*)
- (:maxsize *maxsize-border-width*)
- ((:transient :dialog) *transient-border-width*)))
-
-(defun xwin-class (win)
- (multiple-value-bind (res class) (xlib:get-wm-class win)
- (declare (ignore res))
- class))
-
-(defun xwin-res-name (win)
- (multiple-value-bind (res class) (xlib:get-wm-class win)
- (declare (ignore class))
- res))
-
-(defun xwin-role (win)
- "Return WM_WINDOW_ROLE"
- (let ((name (xlib:get-property win :WM_WINDOW_ROLE)))
- (dformat 10 "role: ~a~%" name)
- (if name
- (utf8-to-string name)
- "")))
-
-(defmacro def-window-attr (attr)
- "Create a new window attribute and corresponding get/set functions."
- (let ((win (gensym))
- (val (gensym)))
- `(progn
- (defun ,(intern (format nil "WINDOW-~a" attr)) (,win)
- (gethash ,attr (window-plist ,win)))
- (defun (setf ,(intern (format nil "WINDOW-~a" attr))) (,val ,win)
- (setf (gethash ,attr (window-plist ,win))) ,val))))
-
-(defun sort-windows (group)
- "Return a copy of the screen's window list sorted by number."
- (sort1 (group-windows group) '< :key 'window-number))
-
-(defun marked-windows (group)
- "Return the marked windows in the specified group."
- (loop for i in (sort-windows group)
- when (window-marked i)
- collect i))
-
-(defun clear-window-marks (group &optional (windows (group-windows group)))
- (dolist (w windows)
- (setf (window-marked w) nil)))
-
-(defun (setf xwin-state) (state xwin)
- "Set the state (iconic, normal, withdrawn) of a window."
- (xlib:change-property xwin
- :WM_STATE
- (list state)
- :WM_STATE
- 32))
-
-(defun xwin-state (xwin)
- "Get the state (iconic, normal, withdraw of a window."
- (first (xlib:get-property xwin :WM_STATE)))
-
-(defun window-hidden-p (window)
- (eql (window-state window) +iconic-state+))
-
-(defun add-wm-state (xwin state)
- (xlib:change-property xwin :_NET_WM_STATE
- (list (xlib:find-atom *display* state))
- :atom 32
- :mode :append))
-
-(defun remove-wm-state (xwin state)
- (xlib:change-property xwin :_NET_WM_STATE
- (delete (xlib:find-atom *display* state) (xlib:get-property xwin :_NET_WM_STATE))
- :atom 32))
-
-(defun window-property (window prop)
- (xlib:get-property (window-xwin window) prop))
-
-(defun find-wm-state (xwin state)
- (find (xlib:find-atom *display* state) (xlib:get-property xwin :_NET_WM_STATE) :test #'=))
-
-(defun xwin-unhide (xwin parent)
- (xlib:map-subwindows parent)
- (xlib:map-window parent)
- (setf (xwin-state xwin) +normal-state+))
-
-(defun unhide-window (window)
- (when (window-in-current-group-p window)
- (xwin-unhide (window-xwin window) (window-parent window)))
- (setf (window-state window) +normal-state+)
- ;; Mark window as unhiden
- (remove-wm-state (window-xwin window) :_NET_WM_STATE_HIDDEN))
-
-;; Despite the naming convention, this function takes a window struct,
-;; not an xlib:window.
-(defun xwin-hide (window)
- (declare (type window window))
- (unless (eq (xlib:window-map-state (window-xwin window)) :unmapped)
- (setf (xwin-state (window-xwin window)) +iconic-state+)
- (incf (window-unmap-ignores window))
- (xlib:unmap-window (window-parent window))
- (xlib:unmap-subwindows (window-parent window))))
-
-(defun hide-window (window)
- (dformat 2 "hide window: ~s~%" window)
- (unless (eql (window-state window) +iconic-state+)
- (setf (window-state window) +iconic-state+)
- ;; Mark window as hidden
- (add-wm-state (window-xwin window) :_NET_WM_STATE_HIDDEN)
- (when (window-in-current-group-p window)
- (xwin-hide window)
- (when (eq window (current-window))
- ;; If this window had the focus, try to avoid losing it.
- (let ((group (window-group window))
- (frame (window-frame window)))
- (setf (frame-window frame)
- (first (remove-if 'window-hidden-p (frame-windows group frame))))
- (focus-frame group (tile-group-current-frame group)))))))
-
-(defun xwin-type (win)
- "Return one of :desktop, :dock, :toolbar, :utility, :splash,
-:dialog, :transient, :maxsize and :normal. Right now
-only :dialog, :normal, :maxsize and :transient are
-actually returned; see +NETWM-WINDOW-TYPES+."
- (or (and (let ((hints (xlib:wm-normal-hints win)))
- (and hints (or (xlib:wm-size-hints-max-width hints)
- (xlib:wm-size-hints-max-height hints)
- (xlib:wm-size-hints-min-aspect hints)
- (xlib:wm-size-hints-max-aspect hints))))
- :maxsize)
- (let ((net-wm-window-type (xlib:get-property win :_NET_WM_WINDOW_TYPE)))
- (when net-wm-window-type
- (dolist (type-atom net-wm-window-type)
- (when (assoc (xlib:atom-name *display* type-atom) +netwm-window-types+)
- (return (cdr (assoc (xlib:atom-name *display* type-atom) +netwm-window-types+)))))))
- (and (xlib:get-property win :WM_TRANSIENT_FOR)
- :transient)
- :normal))
-
-(defun xwin-strut (screen win)
- "Return the area that the window wants to reserve along the edges of the screen.
-Values are left, right, top, bottom, left_start_y, left_end_y,
-right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x
-and bottom_end_x."
- (let ((net-wm-strut-partial (xlib:get-property win :_NET_WM_STRUT_PARTIAL)))
- (if (= (length net-wm-strut-partial) 12)
- (apply 'values net-wm-strut-partial)
- (let ((net-wm-strut (xlib:get-property win :_NET_WM_STRUT)))
- (if (= (length net-wm-strut) 4)
- (apply 'values (concatenate 'list net-wm-strut
- (list 0 (screen-height screen)
- 0 (screen-height screen)
- 0 (screen-width screen)
- 0 (screen-width screen))))
- (values 0 0 0 0 0 0 0 0 0 0 0 0))))))
-
-;; Stolen from Eclipse
-(defun xwin-send-configuration-notify (xwin x y w h bw)
- "Send a synthetic configure notify event to the given window (ICCCM 4.1.5)"
- (xlib:send-event xwin :configure-notify nil
- :event-window xwin
- :window xwin
- :x x :y y
- :width w
- :height h
- :border-width bw
- :propagate-p nil))
-
-(defun update-window-gravity ()
- (dolist (s *screen-list*)
- (dolist (g (screen-groups s))
- (mapc 'maximize-window (group-windows g)))))
-
-(defun set-normal-gravity (gravity)
- "Set the default gravity for normal windows. Possible values are
-@code{:center} @code{:top} @code{:left} @code{:right} @code{:bottom}
-@code{:top-left} @code{:top-right} @code{:bottom-left} and
-@code{:bottom-right}."
- (setf *normal-gravity* gravity)
- (update-window-gravity))
-
-(defun set-maxsize-gravity (gravity)
- "Set the default gravity for maxsize windows."
- (setf *maxsize-gravity* gravity)
- (update-window-gravity))
-
-(defun set-transient-gravity (gravity)
- "Set the default gravity for transient/pop-up windows."
- (setf *transient-gravity* gravity)
- (update-window-gravity))
-
-(defun gravity-for-window (win)
- (or (window-gravity win)
- (ecase (window-type win)
- (:dock *normal-gravity*)
- (:normal *normal-gravity*)
- (:maxsize *maxsize-gravity*)
- ((:transient :dialog) *transient-gravity*))))
-
-(defun geometry-hints (win)
- "Return hints for max width and height and increment hints. These
-hints have been modified to always be defined and never be greater
-than the root window's width and height."
- (let* ((f (window-frame win))
- (x (frame-x f))
- (y (frame-display-y (window-group win) f))
- (border (xlib:drawable-border-width (window-parent win)))
- (fwidth (- (frame-width f) (* 2 border)))
- (fheight (- (frame-display-height (window-group win) f)
- (* 2 border)))
- (width fwidth)
- (height fheight)
- (hints (window-normal-hints win))
- (hints-min-width (and hints (xlib:wm-size-hints-min-width hints)))
- (hints-min-height (and hints (xlib:wm-size-hints-min-height hints)))
- (hints-max-width (and hints (xlib:wm-size-hints-max-width hints)))
- (hints-max-height (and hints (xlib:wm-size-hints-max-height hints)))
- (hints-width (and hints (xlib:wm-size-hints-base-width hints)))
- (hints-height (and hints (xlib:wm-size-hints-base-height hints)))
- (hints-inc-x (and hints (xlib:wm-size-hints-width-inc hints)))
- (hints-inc-y (and hints (xlib:wm-size-hints-height-inc hints)))
- (hints-min-aspect (and hints (xlib:wm-size-hints-min-aspect hints)))
- (hints-max-aspect (and hints (xlib:wm-size-hints-max-aspect hints)))
- center)
- ;; (dformat 4 "hints: ~s~%" hints)
- ;; determine what the width and height should be
- (cond
- ;; handle specially fullscreen windows.
- ((window-fullscreen win)
- (let ((head (frame-head (window-group win) f)))
- (setf x (frame-x head)
- y (frame-y head)
- width (frame-width head)
- height (frame-height head)
- (xlib:window-priority (window-parent win)) :above
- (xlib:drawable-border-width (window-parent win)) 0))
- (return-from geometry-hints (values x y 0 0 width height t)))
- ;; Adjust the defaults if the window is a transient_for window.
- ((find (window-type win) '(:transient :dialog))
- (setf center t
- width (min (max (or hints-width 0)
- (or hints-min-width 0)
- (window-width win))
- width)
- height (min (max (or hints-height 0)
- (or hints-min-height 0)
- (window-height win))
- height)))
- ;; aspect hints are handled similar to max size hints
- ((and hints-min-aspect hints-max-aspect)
- (let ((ratio (/ width height)))
- (cond ((< ratio hints-min-aspect)
- (setf height (ceiling width hints-min-aspect)))
- ((> ratio hints-max-aspect)
- (setf width (ceiling (* height hints-max-aspect)))))
- (setf center t)))
- ;; Update our defaults if the window has the maxsize hints
- ((or hints-max-width hints-max-height)
- (when (and hints-max-width
- (< hints-max-width width))
- (setf width hints-max-width))
- (when (and hints-max-height
- (< hints-max-height height))
- (setf height hints-max-height))
- (setf center t))
- (t
- ;; if they have inc hints then start with the size and adjust
- ;; based on those increments until the window fits in the frame
- (when hints-inc-x
- (let ((w (or hints-width (window-width win))))
- (setf width (+ w (* hints-inc-x
- (+ (floor (- fwidth w) hints-inc-x)))))))
- (when hints-inc-y
- (let ((h (or hints-height (window-height win))))
- (setf height (+ h (* hints-inc-y
- (+ (floor (- fheight h -1) hints-inc-y)))))))))
- ;; adjust for gravity
- (multiple-value-bind (wx wy) (get-gravity-coords (gravity-for-window win)
- width height
- 0 0
- fwidth fheight)
- (when (or center
- (find *window-border-style* '(:tight :none)))
- (setf x (+ wx (frame-x f))
- y (+ wy (frame-display-y (window-group win) f))
- wx 0 wy 0))
- ;; Now return our findings
- (values x y wx wy width height center))))
-
-(defun set-window-geometry (win &key x y width height border-width)
- (macrolet ((update (xfn wfn v)
- `(when ,v ;; (/= (,wfn win) ,v))
- (setf (,xfn (window-xwin win)) ,v)
- ,(when wfn `(setf (,wfn win) ,v)))))
- (xlib:with-state ((window-xwin win))
- (update xlib:drawable-x nil x)
- (update xlib:drawable-y nil y)
- (update xlib:drawable-width window-width width)
- (update xlib:drawable-height window-height height)
- (update xlib:drawable-border-width nil border-width)
- )))
-
-(defun maximize-window (win)
- "Maximize the window."
- (multiple-value-bind (x y wx wy width height stick)
- (geometry-hints win)
- (dformat 4 "maximize window ~a x: ~d y: ~d width: ~d height: ~d stick: ~s~%" win x y width height stick)
- ;; Move the parent window
- (xlib:with-state ((window-parent win))
- (setf (xlib:drawable-x (window-parent win)) x
- (xlib:drawable-y (window-parent win)) y))
- ;; This is the only place a window's geometry should change
- (set-window-geometry win :x wx :y wy :width width :height height :border-width 0)
- (xlib:with-state ((window-parent win))
- ;; FIXME: updating the border doesn't need to be run everytime
- ;; the window is maximized, but only when the border style or
- ;; window type changes. The overhead is probably minimal,
- ;; though.
- (setf (xlib:drawable-border-width (window-parent win))
- (case *window-border-style*
- (:none 0)
- (t (default-border-width-for-type (window-type win)))))
- ;; the parent window should stick to the size of the window
- ;; unless it isn't being maximized to fill the frame.
- (if (or stick
- (find *window-border-style* '(:tight :none)))
- (setf (xlib:drawable-width (window-parent win)) (window-width win)
- (xlib:drawable-height (window-parent win)) (window-height win))
- (let ((frame (window-frame win)))
- (setf (xlib:drawable-width (window-parent win)) (- (frame-width frame)
- (* 2 (xlib:drawable-border-width (window-parent win))))
- (xlib:drawable-height (window-parent win)) (- (frame-display-height (window-group win) frame)
- (* 2 (xlib:drawable-border-width (window-parent win))))))))))
-
-(defun find-free-window-number (group)
- "Return a free window number for GROUP."
- (find-free-number (mapcar 'window-number (group-windows group))))
-
-(defun reparent-window (window)
- ;; apparently we need to grab the server so the client doesn't get
- ;; the mapnotify event before the reparent event. that's what fvwm
- ;; says.
- (xlib:with-server-grabbed (*display*)
- (let* ((screen (window-screen window))
- (master-window (xlib:create-window
- :parent (screen-root screen)
- :x (xlib:drawable-x (window-xwin window)) :y (xlib:drawable-y (window-xwin window))
- :width (window-width window)
- :height (window-height window)
- ;; normal windows get a black background
- :background (if (eq (window-type window) :normal)
- (screen-bg-color screen)
- :none)
- :border (screen-unfocus-color screen)
- :border-width (default-border-width-for-type (window-type window))
- :event-mask *window-parent-events*)))
- (unless (eq (xlib:window-map-state (window-xwin window)) :unmapped)
- (incf (window-unmap-ignores window)))
- (xlib:reparent-window (window-xwin window) master-window 0 0)
- (xwin-grab-buttons master-window)
- ;; ;; we need to update these values since they get set to 0,0 on reparent
- ;; (setf (window-x window) 0
- ;; (window-y window) 0)
- (xlib:add-to-save-set (window-xwin window))
- (setf (window-parent window) master-window))))
-
-(defun process-existing-windows (screen)
- "Windows present when stumpwm starts up must be absorbed by stumpwm."
- (let ((children (xlib:query-tree (screen-root screen)))
- (*processing-existing-windows* t)
- (stacking (xlib:get-property (screen-root screen) :_NET_CLIENT_LIST_STACKING :type :window)))
- (when stacking
- (dformat 3 "Using window stacking: ~{~X ~}~%" stacking)
- ;; sort by _NET_CLIENT_LIST_STACKING
- (setf children (stable-sort children #'< :key
- (lambda (xwin)
- (or (position (xlib:drawable-id xwin) stacking :test #'=) 0)))))
- (dolist (win children)
- (let ((map-state (xlib:window-map-state win))
- (wm-state (xwin-state win)))
- ;; Don't process override-redirect windows.
- (unless (or (eq (xlib:window-override-redirect win) :on)
- (internal-window-p screen win))
- (if (eq (xwin-type win) :dock)
- (progn
- (dformat 1 "Window ~S is dock-type. Placing in mode-line.~%" win)
- (place-mode-line-window screen win))
- (if (or (eql map-state :viewable)
- (eql wm-state +iconic-state+))
- (progn
- (dformat 1 "Processing ~S ~S~%" (xwin-name win) win)
- (process-mapped-window screen win))))))))
- (dolist (w (screen-windows screen))
- (setf (window-state w) +normal-state+)
- (xwin-hide w)))
-
-(defun xwin-grab-keys (win)
- (labels ((grabit (w key)
- (let ((code (xlib:keysym->keycodes *display* (key-keysym key))))
- ;; some keysyms aren't mapped to keycodes so just ignore them.
- (when code
- (xlib:grab-key w code
- :modifiers (x11-mods key) :owner-p t
- :sync-pointer-p nil :sync-keyboard-p nil)
- ;; Ignore numlock by also grabbing the keycombo with
- ;; numlock on.
- (when (modifiers-numlock *modifiers*)
- (xlib:grab-key w code
- :modifiers (x11-mods key t) :owner-p t
- :sync-pointer-p nil :sync-keyboard-p nil))))))
- (maphash (lambda (k v)
- (declare (ignore v))
- (grabit win k))
- *top-map*)))
-
-(defun grab-keys-on-window (win)
- (xwin-grab-keys (window-xwin win)))
-
-(defun xwin-ungrab-keys (win)
- (xlib:ungrab-key win :any :modifiers :any))
-
-(defun ungrab-keys-on-window (win)
- (xwin-ungrab-keys (window-xwin win)))
-
-(defun xwin-grab-buttons (win)
- ;; FIXME: Why doesn't grabbing button :any work? We have to
- ;; grab them one by one instead.
- (xwin-ungrab-buttons win)
- (loop for i from 1 to 7
- do (xlib:grab-button win i '(:button-press)
- :modifiers :any
- :owner-p nil
- :sync-pointer-p t
- :sync-keyboard-p nil)))
-
-
-(defun xwin-ungrab-buttons (win)
- (xlib:ungrab-button win :any :modifiers :any))
-
-(defun sync-keys ()
- "Any time *top-map* is modified this must be called."
- (loop for i in *screen-list*
- do (xwin-ungrab-keys (screen-focus-window i))
- do (loop for j in (screen-mapped-windows i)
- do (xwin-ungrab-keys j))
- do (xlib:display-finish-output *display*)
- do (loop for j in (screen-mapped-windows i)
- do (xwin-grab-keys j))
- do (xwin-grab-keys (screen-focus-window i)))
- (xlib:display-finish-output *display*))
-
-
-;;; Window placement routines
-
-(defun xwin-to-window (xwin)
- "Build a window for XWIN"
- (make-window
- :xwin xwin
- :width (xlib:drawable-width xwin) :height (xlib:drawable-height xwin)
- :x (xlib:drawable-x xwin) :y (xlib:drawable-y xwin)
- :title (xwin-name xwin)
- :class (xwin-class xwin)
- :res (xwin-res-name xwin)
- :role (xwin-role xwin)
- :type (xwin-type xwin)
- :normal-hints (xlib:wm-normal-hints xwin)
- :state +iconic-state+
- :plist (make-hash-table)
- :unmap-ignores 0))
-
-(defun string-match (string pat)
- (let ((l (length pat)))
- (when (> l 0)
- (if (and (> l 3) (equal (subseq pat 0 3) "..."))
- (search (subseq pat 3 l) string)
- (equal string pat)))))
-
-(defun window-matches-properties-p (window &key class instance type role title)
- "Returns T if window matches all the given properties"
- (and
- (if class (equal (window-class window) class) t)
- (if instance (equal (window-res window) instance) t)
- (if type (equal (window-type window) type) t)
- (if role (string-match (window-role window) role) t)
- (if title (string-match (window-title window) title) t) t))
-
-(defun window-matches-rule-p (w rule)
- "Returns T if window matches rule"
- (destructuring-bind (group-name frame raise lock &rest props) rule
- (declare (ignore frame raise))
- (if (or lock
- (equal group-name (group-name (or (window-group w) (current-group)))))
- (apply 'window-matches-properties-p w props))))
-
-;; TODO: add rules allowing matched windows to create their own groups/frames
-
-(defun rule-matching-window (window)
- (dolist (rule *window-placement-rules*)
- (when (window-matches-rule-p window rule) (return rule))))
-
-(defun get-window-placement (screen window)
- "Returns the ideal group and frame that WINDOW should belong to and whether
- the window should be raised."
- (let ((match (rule-matching-window window)))
- (if match
- (destructuring-bind (group-name frame raise lock &rest props) match
- (declare (ignore lock props))
- (let ((group (find-group screen group-name)))
- (if group
- (values group (frame-by-number group frame) raise)
- (progn
- (message "^B^1*Error placing window, group \"^b~a^B\" does not exist." group-name)
- (values)))))
- (values))))
-
-(defun sync-window-placement ()
- "Re-arrange existing windows according to placement rules"
- (dolist (screen *screen-list*)
- (dolist (window (screen-windows screen))
- (multiple-value-bind (to-group frame raise) (get-window-placement screen window)
- (declare (ignore raise))
- (when to-group
- (unless (eq (window-group window) to-group)
- (move-window-to-group window to-group))
- (unless (eq (window-frame window) frame)
- (pull-window window frame)))))))
-
-(defun assign-window (window group frame &optional (where :tail))
- (setf (window-group window) group
- (window-number window) (find-free-window-number group)
- (window-frame window) (or frame (pick-prefered-frame window)))
- (if (eq where :head)
- (push window (group-windows group))
- (setf (group-windows group) (append (group-windows group) (list window)))))
-
-(defun place-existing-window (screen xwin)
- "Called for windows existing at startup."
- (let* ((window (xwin-to-window xwin))
- (netwm-id (first (xlib:get-property xwin :_NET_WM_DESKTOP)))
- (group (if (and netwm-id (< netwm-id (length (screen-groups screen))))
- (elt (sort-groups screen) netwm-id)
- (screen-current-group screen))))
- (dformat 3 "Assigning pre-existing window ~S to group ~S~%" (window-name window) (group-name group))
- (assign-window window group (find-frame group (xlib:drawable-x xwin) (xlib:drawable-y xwin)) :head)
- (setf (frame-window (window-frame window)) window)
- window))
-
-(defun place-window (screen xwin)
- "Pick a group and frame for XWIN."
- (let* ((window (xwin-to-window xwin))
- (group (screen-current-group screen))
- (frame nil)
- (raise nil))
- (multiple-value-bind (to-group to-frame to-raise) (get-window-placement screen window)
- (setf group (or to-group group)
- frame to-frame
- raise to-raise))
- (assign-window window group frame)
- (setf (xwin-state xwin) +iconic-state+)
- (xlib:change-property xwin :_NET_WM_DESKTOP
- (list (netwm-group-id group))
- :cardinal 32)
- (when frame
- (unless (eq (current-group) group)
- (if raise
- (switch-to-group group)
- (message "Placing window ~a in frame ~d of group ~a"
- (window-name window) (frame-number frame) (group-name group))))
- (when raise
- (switch-to-screen (group-screen group))
- (focus-frame group frame))
- (run-hook-with-args *place-window-hook* window group frame))
- window))
-
-(defun pick-prefered-frame (window)
- (let* ((group (window-group window))
- (frames (group-frames group))
- (default (tile-group-current-frame group)))
- (or
- (if (or (functionp *new-window-prefered-frame*)
- (and (symbolp *new-window-prefered-frame*)
- (fboundp *new-window-prefered-frame*)))
- (handler-case
- (funcall *new-window-prefered-frame* window)
- (error (c)
- (message "^1*^BError while calling ^b^3**new-window-prefered-frame*^1*^B: ^n~a" c)
- nil))
- (loop for i in *new-window-prefered-frame*
- thereis (case i
- (:last
- ;; last-frame can be stale
- (and (> (length frames) 1)
- (tile-group-last-frame group)))
- (:unfocused
- (find-if (lambda (f)
- (not (eq f (tile-group-current-frame group))))
- frames))
- (:empty
- (find-if (lambda (f)
- (null (frame-window f)))
- frames))
- (:choice
- ;; Transient windows sometimes specify a location
- ;; relative to the TRANSIENT_FOR window. Just ignore
- ;; these hints.
- (unless (find (window-type window) '(:transient :dialog))
- (let ((hints (window-normal-hints window)))
- (when (and hints (xlib:wm-size-hints-user-specified-position-p hints))
- (find-frame group (window-x window) (window-y window))))))
- (t ; :focused
- (tile-group-current-frame group)))))
- default)))
-
-(defun add-window (screen xwin)
- (screen-add-mapped-window screen xwin)
- (register-window (if *processing-existing-windows*
- (place-existing-window screen xwin)
- (place-window screen xwin))))
-
-(defun netwm-remove-window (window)
- (xlib:delete-property (window-xwin window) :_NET_WM_DESKTOP))
-
-(defun process-mapped-window (screen xwin)
- "Add the window to the screen's mapped window list and process it as
-needed."
- (let ((window (add-window screen xwin)))
- (setf (xlib:window-event-mask (window-xwin window)) *window-events*)
- ;; windows always have border width 0. Their parents provide the
- ;; border.
- (set-window-geometry window :border-width 0)
- (reparent-window window)
- (maximize-window window)
- (grab-keys-on-window window)
- ;; quite often the modeline displays the window list, so update it
- (update-all-mode-lines)
- ;; Set allowed actions
- (xlib:change-property xwin :_NET_WM_ALLOWED_ACTIONS
- (mapcar (lambda (a)
- (xlib:intern-atom *display* a))
- +netwm-allowed-actions+)
- :atom 32)
- ;; Run the new window hook on it.
- (run-hook-with-args *new-window-hook* window)
- window))
-
-(defun find-withdrawn-window (xwin)
- "Return the window and screen for a withdrawn window."
- (declare (type xlib:window xwin))
- (dolist (i *screen-list*)
- (let ((w (find xwin (screen-withdrawn-windows i) :key 'window-xwin :test 'xlib:window-equal)))
- (when w
- (return-from find-withdrawn-window (values w i))))))
-
-(defun restore-window (window)
- "Restore a withdrawn window"
- (declare (type window window))
- ;; put it in a valid group
- (let ((screen (window-screen window)))
- ;; Use window plaecment rules
- (multiple-value-bind (group frame raise) (get-window-placement screen window)
- (declare (ignore raise))
- (unless (find (window-group window)
- (screen-groups screen))
- (setf (window-group window) (or group (screen-current-group screen))))
- ;; FIXME: somehow it feels like this could be merged with group-add-window
- (setf (window-title window) (xwin-name (window-xwin window))
- (window-class window) (xwin-class (window-xwin window))
- (window-res window) (xwin-res-name (window-xwin window))
- (window-role window) (xwin-role (window-xwin window))
- (window-type window) (xwin-type (window-xwin window))
- (window-normal-hints window) (xlib:wm-normal-hints (window-xwin window))
- (window-number window) (find-free-window-number (window-group window))
- (window-state window) +iconic-state+
- (xwin-state (window-xwin window)) +iconic-state+
- (screen-withdrawn-windows screen) (delete window (screen-withdrawn-windows screen))
- ;; put the window at the end of the list
- (group-windows (window-group window)) (append (group-windows (window-group window)) (list window))
- (window-frame window) (or frame (pick-prefered-frame window))))
- (screen-add-mapped-window screen (window-xwin window))
- (register-window window)
- (xlib:change-property (window-xwin window) :_NET_WM_DESKTOP
- (list (netwm-group-id (window-group window)))
- :cardinal 32)
- (maximize-window window)
- ;; It is effectively a new window in terms of the window list.
- (run-hook-with-args *new-window-hook* window)
- ;; give it focus
- (if (deny-request-p window *deny-map-request*)
- (unless *suppress-deny-messages*
- (if (eq (window-group window) (current-group))
- (echo-string (window-screen window) (format nil "'~a' denied map request" (window-name window)))
- (echo-string (window-screen window) (format nil "'~a' denied map request in group ~a" (window-name window) (group-name (window-group window))))))
- (frame-raise-window (window-group window) (window-frame window) window
- (if (eq (window-frame window)
- (tile-group-current-frame (window-group window)))
- t nil)))))
-
-(defun withdraw-window (window)
- "Withdrawing a window means just putting it in a list til we get a destroy event."
- (declare (type window window))
- ;; This function cannot request info about WINDOW from the xserver as it may not exist anymore.
- (let ((f (window-frame window))
- (group (window-group window))
- (screen (window-screen window)))
- (dformat 1 "withdraw window ~a~%" screen)
- ;; Save it for later since it is only withdrawn, not destroyed.
- (push window (screen-withdrawn-windows screen))
- (setf (window-state window) +withdrawn-state+
- (xwin-state (window-xwin window)) +withdrawn-state+)
- (xlib:unmap-window (window-parent window))
- ;; Clean up the window's entry in the screen and group
- (screen-remove-mapped-window screen (window-xwin window))
- (setf (group-windows group)
- (delete window (group-windows group)))
- ;; remove it from it's frame structures
- (when (eq (frame-window f) window)
- (frame-raise-window group f (first (frame-windows group f)) nil))
- (when (window-in-current-group-p window)
- ;; since the window doesn't exist, it doesn't have focus.
- (setf (screen-focus screen) nil))
- (netwm-remove-window window)
- ;; If the current window was removed, then refocus the frame it
- ;; was in, since it has a new current window
- (when (eq (tile-group-current-frame group) f)
- (focus-frame (window-group window) f))
- ;; quite often the modeline displays the window list, so update it
- (update-all-mode-lines)
- ;; Run the destroy hook on the window
- (run-hook-with-args *destroy-window-hook* window)))
-
-(defun destroy-window (window)
- (declare (type window window))
- "The window has been destroyed. clean up our data structures."
- ;; This function cannot request info about WINDOW from the xserver
- (let ((screen (window-screen window)))
- (unless (eql (window-state window) +withdrawn-state+)
- (withdraw-window window))
- ;; now that the window is withdrawn, clean up the data structures
- (setf (screen-withdrawn-windows screen)
- (delete window (screen-withdrawn-windows screen)))
- (dformat 1 "destroy window ~a~%" screen)
- (dformat 3 "destroying parent window~%")
- (xlib:destroy-window (window-parent window))))
-
-(defun move-window-to-head (group window)
- "Move window to the head of the group's window list."
- (declare (type group group))
- (declare (type window window))
- ;(assert (member window (screen-mapped-windows screen)))
- (setf (group-windows group) (delete window (group-windows group)))
- (push window (group-windows group))
- (netwm-update-client-list-stacking (group-screen group)))
-
-(defun no-focus (group last-win)
- "don't focus any window but still read keyboard events."
- (dformat 3 "no-focus~%")
- (let* ((screen (group-screen group)))
- (when (eq group (screen-current-group screen))
- (xlib:set-input-focus *display* (screen-focus-window screen) :POINTER-ROOT)
- (setf (screen-focus screen) nil)
- (move-screen-to-head screen))
- (when last-win
- (update-window-border last-win))))
-
-(defun focus-window (window)
- "Give the window focus. This means the window will be visible,
-maximized, and given focus."
- (dformat 3 "focus-window: ~s~%" window)
- (let* ((group (window-group window))
- (screen (group-screen group))
- (cw (screen-focus screen)))
- ;; If window to focus is already focused then our work is done.
- (unless (eq window cw)
- (update-all-mode-lines)
- (raise-window window)
- (screen-set-focus screen window)
- ;;(send-client-message window :WM_PROTOCOLS +wm-take-focus+)
- (update-window-border window)
- (when cw
- (update-window-border cw))
- ;; Move the window to the head of the mapped-windows list
- (move-window-to-head group window)
- (run-hook-with-args *focus-window-hook* window cw))))
-
-(defun delete-window (window)
- "Send a delete event to the window."
- (dformat 3 "Delete window~%")
- (send-client-message window :WM_PROTOCOLS (xlib:intern-atom *display* :WM_DELETE_WINDOW)))
-
-(defun xwin-kill (window)
- "Kill the client associated with window."
- (dformat 3 "Kill client~%")
- (xlib:kill-client *display* (xlib:window-id window)))
-
-
-;;; Message printing functions
-
-(defun color-exists-p (color)
- (handler-case
- (loop for i in *screen-list*
- always (xlib:lookup-color (xlib:screen-default-colormap (screen-number i)) color))
- (xlib:name-error () nil)))
-
-(defun font-exists-p (font-name)
- ;; if we can list the font then it exists
- (plusp (length (xlib:list-font-names *display* font-name :max-fonts 1))))
-
-(defmacro set-any-color (val color)
- `(progn (dolist (s *screen-list*)
- (setf (,val s) (alloc-color s ,color)))
- (update-colors-all-screens)))
-
-;; FIXME: I don't like any of this. Isn't there a way to define
-;; a setf method to call (update-colors-all-screens) when the user
-;; does eg. (setf *foreground-color* "green") instead of having
-;; these redundant set-foo functions?
-(defun set-fg-color (color)
- "Set the foreground color for the message bar and input
-bar. @var{color} can be any color recognized by X."
- (setf *text-color* color)
- (set-any-color screen-fg-color color))
-
-(defun set-bg-color (color)
- "Set the background color for the message bar and input
-bar. @var{color} can be any color recognized by X."
- (set-any-color screen-bg-color color))
-
-(defun set-border-color (color)
- "Set the border color for the message bar and input
-bar. @var{color} can be any color recognized by X."
- (set-any-color screen-border-color color))
-
-(defun set-win-bg-color (color)
- "Set the background color of the window. The background color will only
-be visible for windows with size increment hints such as @samp{emacs}
-and @samp{xterm}."
- (set-any-color screen-win-bg-color color))
-
-(defun set-focus-color (color)
- (set-any-color screen-focus-color color))
-
-(defun set-unfocus-color (color)
- (set-any-color screen-unfocus-color color))
-
-(defun set-msg-border-width (width)
- "Set the border width for the message bar and input
-bar."
- (check-type width (integer 0))
- (dolist (i *screen-list*)
- (setf (screen-msg-border-width i) width))
- (update-border-all-screens)
- t)
-
-(defun set-frame-outline-width (width)
- (check-type width (integer 0))
- (dolist (i *screen-list*)
- (setf (screen-frame-outline-width i) (if (oddp width) (1+ width) width)
- (xlib:gcontext-line-width (screen-frame-outline-gc i)) (screen-frame-outline-width i)))
- (update-border-all-screens)
- t)
-
-(defun set-font (font)
- "Set the font for the message bar and input bar."
- (when (font-exists-p font)
- (dolist (i *screen-list*)
- (let ((fobj (xlib:open-font *display* (first (xlib:list-font-names *display* font :max-fonts 1)))))
- (xlib:close-font (screen-font i))
- (setf (screen-font i) fobj
- (xlib:gcontext-font (screen-message-gc i)) fobj)
- ;; update the modelines too
- (dolist (h (screen-heads i))
- (when (and (head-mode-line h)
- (eq (mode-line-mode (head-mode-line h)) :stump))
- (setf (xlib:gcontext-font (mode-line-gc (head-mode-line h))) fobj)
- (resize-mode-line (head-mode-line h))
- (sync-mode-line (head-mode-line h))))))
- t))
-
-(defun max-width (font l)
- "Return the width of the longest string in L using FONT."
- (loop for i in l
- maximize (xlib:text-width font i :translate #'translate-id)))
-
-(defun get-gravity-coords (gravity width height minx miny maxx maxy)
- "Return the x y coords for a window on with gravity etc"
- (values (case gravity
- ((:top-right :bottom-right :right) (- maxx width))
- ((:top :bottom :center) (truncate (- maxx minx width) 2))
- (t minx))
- (case gravity
- ((:bottom-left :bottom-right :bottom) (- maxy height))
- ((:left :right :center) (truncate (- maxy miny height) 2))
- (t miny))))
-
-(defun setup-win-gravity (screen win gravity)
- "Position the x, y of the window according to its gravity. This
-function expects to be wrapped in a with-state for win."
- (xlib:with-state ((screen-root screen))
- (let ((w (xlib:drawable-width win))
- (h (xlib:drawable-height win))
- (screen-width (head-width (current-head)))
- (screen-height (head-height (current-head))))
- (let ((x (case gravity
- ((:top-left :bottom-left) 0)
- (:center (truncate (- screen-width w (* (xlib:drawable-border-width win) 2)) 2))
- (t (- screen-width w (* (xlib:drawable-border-width win) 2)))))
- (y (case gravity
- ((:bottom-right :bottom-left) (- screen-height h (* (xlib:drawable-border-width win) 2)))
- (:center (truncate (- screen-height h (* (xlib:drawable-border-width win) 2)) 2))
- (t 0))))
- (setf (xlib:drawable-y win) (max (head-y (current-head)) (+ (head-y (current-head)) y))
- (xlib:drawable-x win) (max (head-x (current-head)) (+ (head-x (current-head)) x)))))))
-
-(defun setup-message-window (screen lines width)
- (let ((height (* lines
- (+ (xlib:font-ascent (screen-font screen))
- (xlib:font-descent (screen-font screen)))))
- (win (screen-message-window screen)))
- ;; Now that we know the dimensions, raise and resize it.
- (xlib:with-state (win)
- (setf (xlib:drawable-height win) height
- (xlib:drawable-width win) (+ width (* *message-window-padding* 2))
- (xlib:window-priority win) :above)
- (setup-win-gravity screen win *message-window-gravity*))
- (xlib:map-window win)
- ;; Clear the window
- (xlib:clear-area win)
- (incf (screen-ignore-msg-expose screen))
- ;; Have to flush this or the window might get cleared
- ;; after we've already started drawing it.
- (xlib:display-finish-output *display*)))
-
-(defun invert-rect (screen win x y width height)
- "invert the color in the rectangular area. Used for highlighting text."
- (let ((gcontext (xlib:create-gcontext :drawable win
- :foreground (screen-fg-color screen)
- :function boole-xor)))
- (xlib:draw-rectangle win gcontext x y width height t)
- (setf (xlib:gcontext-foreground gcontext) (screen-bg-color screen))
- (xlib:draw-rectangle win gcontext x y width height t)))
-
-
-;;; Frame functions
-
-(defun populate-frames (group)
- "Try to fill empty frames in GROUP with hidden windows"
- (dolist (f (group-frames group))
- (unless (frame-window f)
- (choose-new-frame-window f group)
- (when (frame-window f)
- (maximize-window (frame-window f))
- (unhide-window (frame-window f))))))
-
-(defun frame-by-number (group n)
- (unless (eq n nil)
- (find n (group-frames group)
- :key 'frame-number
- :test '=)))
-
-(defun find-frame (group x y)
- "Return the frame of GROUP containing the pixel at X Y"
- (dolist (f (group-frames group))
- (let* ((fy (frame-y f))
- (fx (frame-x f))
- (fwx (+ fx (frame-width f)))
- (fhy (+ fy (frame-height f))))
- (when (and
- (>= y fy) (<= y fhy)
- (>= x fx) (<= x fwx)
- (return f))))))
-
-
-(defun frame-set-x (frame v)
- (decf (frame-width frame)
- (- v (frame-x frame)))
- (setf (frame-x frame) v))
-
-(defun frame-set-y (frame v)
- (decf (frame-height frame)
- (- v (frame-y frame)))
- (setf (frame-y frame) v))
-
-(defun frame-set-r (frame v)
- (setf (frame-width frame)
- (- v (frame-x frame))))
-
-(defun frame-set-b (frame v)
- (setf (frame-height frame)
- (- v (frame-y frame))))
-
-(defun frame-r (frame)
- (+ (frame-x frame) (frame-width frame)))
-
-(defun frame-b (frame)
- (+ (frame-y frame) (frame-height frame)))
-
-(defun frame-intersect (f1 f2)
- "Return a new frame representing (only) the intersection of F1 and F2. WIDTH and HEIGHT will be <= 0 if there is no overlap"
- (let ((r (copy-frame f1)))
- (when (> (frame-x f2) (frame-x f1))
- (frame-set-x r (frame-x f2)))
- (when (< (+ (frame-x f2) (frame-width f2))
- (+ (frame-x f1) (frame-width f1)))
- (frame-set-r r (frame-r f2)))
- (when (> (frame-y f2) (frame-y f1))
- (frame-set-y r (frame-y f2)))
- (when (< (+ (frame-y f2) (frame-height f2))
- (+ (frame-y f1) (frame-height f1)))
- (frame-set-b r (frame-b f2)))
- (values r)))
-
-(defun frames-overlap-p (f1 f2)
- "Returns T if frames F1 and F2 overlap at all"
- (and (and (frame-p f1) (frame-p f2))
- (let ((frame (frame-intersect f1 f2)))
- (values (not (and (plusp (frame-width frame))
- (plusp (frame-height frame))))))))
-
-(defun frame-raise-window (g f w &optional (focus t))
- "Raise the window w in frame f in group g. if FOCUS is
-T (default) then also focus the frame."
- (let ((oldw (frame-window f)))
- ;; nothing to do when W is nil
- (setf (frame-window f) w)
- (unless (and w (eq oldw w))
- (if w
- (raise-window w)
- (mapc 'hide-window (frame-windows g f))))
- (when focus
- (focus-frame g f))
- (when (and w (not (window-modal-p w)))
- (raise-modals-of w))))
-
-(defun focus-frame (group f)
- (let ((w (frame-window f))
- (last (tile-group-current-frame group))
- (show-indicator nil))
- (setf (tile-group-current-frame group) f)
- ;; record the last frame to be used in the fother command.
- (unless (eq f last)
- (setf (tile-group-last-frame group) last)
- (run-hook-with-args *focus-frame-hook* f last)
- (setf show-indicator t))
- (if w
- (focus-window w)
- (no-focus group (frame-window last)))
- (if show-indicator
- (show-frame-indicator group)
- (show-frame-outline group))))
-
-(defun frame-windows (group f)
- (remove-if-not (lambda (w) (eq (window-frame w) f))
- (group-windows group)))
-
-(defun frame-sort-windows (group f)
- (remove-if-not (lambda (w) (eq (window-frame w) f))
- (sort-windows group)))
-
-(defun copy-frame-tree (tree)
- "Return a copy of the frame tree."
- (cond ((null tree) tree)
- ((typep tree 'frame)
- (copy-structure tree))
- (t
- (mapcar #'copy-frame-tree tree))))
-
-(defun group-frames (group)
- (tree-accum-fn (tile-group-frame-tree group) 'nconc 'list))
-
-(defun head-frames (group head)
- (tree-accum-fn (tile-group-frame-head group head) 'nconc 'list))
-
-(defun find-free-frame-number (group)
- (find-free-number (mapcar (lambda (f) (frame-number f))
- (group-frames group))))
-
-(defun choose-new-frame-window (frame group)
- "Find out what window should go in a newly created frame."
- (let ((win (case *new-frame-action*
- (:last-window (other-hidden-window group))
- (t nil))))
- (setf (frame-window frame) win)
- (when win
- (setf (window-frame win) frame))))
-
-(defun split-frame-h (group p)
- "Return 2 new frames. The first one stealing P's number and window"
- (let* ((w (truncate (/ (frame-width p) 2)))
- (h (frame-height p))
- (f1 (make-frame :number (frame-number p)
- :x (frame-x p)
- :y (frame-y p)
- :width w
- :height h
- :window (frame-window p)))
- (f2 (make-frame :number (find-free-frame-number group)
- :x (+ (frame-x p) w)
- :y (frame-y p)
- ;; gobble up the modulo
- :width (- (frame-width p) w)
- :height h
- :window nil)))
- (run-hook-with-args *new-frame-hook* f2)
- (values f1 f2)))
-
-(defun split-frame-v (group p)
- "Return 2 new frames. The first one stealing P's number and window"
- (let* ((w (frame-width p))
- (h (truncate (/ (frame-height p) 2)))
- (f1 (make-frame :number (frame-number p)
- :x (frame-x p)
- :y (frame-y p)
- :width w
- :height h
- :window (frame-window p)))
- (f2 (make-frame :number (find-free-frame-number group)
- :x (frame-x p)
- :y (+ (frame-y p) h)
- :width w
- ;; gobble up the modulo
- :height (- (frame-height p) h)
- :window nil)))
- (run-hook-with-args *new-frame-hook* f2)
- (values f1 f2)))
-
-(defun funcall-on-leaf (tree leaf fn)
- "Return a new tree with LEAF replaced with the result of calling FN on LEAF."
- (cond ((atom tree)
- (if (eq leaf tree)
- (funcall fn leaf)
- tree))
- (t (mapcar (lambda (sib)
- (funcall-on-leaf sib leaf fn))
- tree))))
-
-(defun funcall-on-node (tree fn match)
- "Call fn on the node where match returns t."
- (if (funcall match tree)
- (funcall fn tree)
- (cond ((atom tree) tree)
- (t (mapcar (lambda (sib)
- (funcall-on-node sib fn match))
- tree)))))
-
-(defun replace-frame-in-tree (tree f &rest frames)
- (funcall-on-leaf tree f (lambda (f)
- (declare (ignore f))
- frames)))
-
-(defun sibling-internal (tree leaf fn)
- "helper for next-sibling and prev-sibling."
- (cond ((atom tree) nil)
- ((find leaf tree)
- (let* ((rest (cdr (member leaf (funcall fn tree))))
- (pick (car (if (null rest) (funcall fn tree) rest))))
- (unless (eq pick leaf)
- pick)))
- (t (find-if (lambda (x)
- (sibling-internal x leaf fn))
- tree))))
-
-(defun next-sibling (tree leaf)
- "Return the sibling of LEAF in TREE."
- (sibling-internal tree leaf 'identity))
-
-(defun prev-sibling (tree leaf)
- (sibling-internal tree leaf 'reverse))
-
-(defun closest-sibling (tree leaf)
- "Return the sibling to the right/below of leaf or left/above if
-leaf is the most right/below of its siblings."
- (let* ((parent (tree-parent tree leaf))
- (lastp (= (position leaf parent) (1- (length parent)))))
- (if lastp
- (prev-sibling parent leaf)
- (next-sibling parent leaf))))
-
-(defun migrate-frame-windows (group src dest)
- "Migrate all windows in SRC frame to DEST frame."
- (mapc (lambda (w)
- (when (eq (window-frame w) src)
- (setf (window-frame w) dest)))
- (group-windows group)))
-
-(defun tree-accum-fn (tree acc fn)
- "Run an accumulator function on fn applied to each leaf"
- (cond ((null tree) nil)
- ((atom tree)
- (funcall fn tree))
- (t (apply acc (mapcar (lambda (x) (tree-accum-fn x acc fn)) tree)))))
-
-(defun tree-iterate (tree fn)
- "Call FN on every leaf in TREE"
- (cond ((null tree) nil)
- ((atom tree)
- (funcall fn tree))
- (t (mapc (lambda (x) (tree-iterate x fn)) tree))))
-
-(defun tree-x (tree)
- (tree-accum-fn tree 'min 'frame-x))
-
-(defun tree-y (tree)
- (tree-accum-fn tree 'min 'frame-y))
-
-(defun tree-width (tree)
- (cond ((atom tree) (frame-width tree))
- ((tree-row-split tree)
- ;; in row splits, all children have the same width, so use the
- ;; first one.
- (tree-width (first tree)))
- (t
- ;; for column splits we add the width of each child
- (reduce '+ tree :key 'tree-width))))
-
-(defun tree-height (tree)
- (cond ((atom tree) (frame-height tree))
- ((tree-column-split tree)
- ;; in row splits, all children have the same width, so use the
- ;; first one.
- (tree-height (first tree)))
- (t
- ;; for column splits we add the width of each child
- (reduce '+ tree :key 'tree-height))))
-
-(defun tree-parent (top node)
- "Return the list in TOP that contains NODE."
- (cond ((atom top) nil)
- ((find node top) top)
- (t (loop for i in top
- thereis (tree-parent i node)))))
-
-(defun tree-leaf (top)
- "Return a leaf of the tree. Use this when you need a leaf but
-you don't care which one."
- (tree-accum-fn top
- (lambda (&rest siblings)
- (car siblings))
- #'identity))
-
-(defun tree-row-split (tree)
- "Return t if the children of tree are stacked vertically"
- (loop for i in (cdr tree)
- with head = (car tree)
- always (= (tree-x head) (tree-x i))))
-
-(defun tree-column-split (tree)
- "Return t if the children of tree are side-by-side"
- (loop for i in (cdr tree)
- with head = (car tree)
- always (= (tree-y head) (tree-y i))))
-
-(defun tree-split-type (tree)
- "return :row or :column"
- (cond ((tree-column-split tree) :column)
- ((tree-row-split tree) :row)
- (t (error "tree-split-type unknown"))))
-
-(defun offset-tree (tree x y)
- "move the screen's frames around."
- (tree-iterate tree (lambda (frame)
- (incf (frame-x frame) x)
- (incf (frame-y frame) y))))
-
-(defun offset-tree-dir (tree amount dir)
- (ecase dir
- (:left (offset-tree tree (- amount) 0))
- (:right (offset-tree tree amount 0))
- (:top (offset-tree tree 0 (- amount)))
- (:bottom (offset-tree tree 0 amount))))
-
-(defun expand-tree (tree amount dir)
- "expand the frames in tree by AMOUNT in DIR direction. DIR can be :top :bottom :left :right"
- (labels ((expand-frame (f amount dir)
- (ecase dir
- (:left (decf (frame-x f) amount)
- (incf (frame-width f) amount))
- (:right (incf (frame-width f) amount))
- (:top (decf (frame-y f) amount)
- (incf (frame-height f) amount))
- (:bottom (incf (frame-height f) amount)))))
- (cond ((null tree) nil)
- ((atom tree)
- (expand-frame tree amount dir))
- ((or (and (find dir '(:left :right))
- (tree-row-split tree))
- (and (find dir '(:top :bottom))
- (tree-column-split tree)))
- (dolist (i tree)
- (expand-tree i amount dir)))
- (t
- (let* ((children (if (find dir '(:left :top))
- (reverse tree)
- tree))
- (sz-fn (if (find dir '(:left :right))
- 'tree-width
- 'tree-height))
- (total (funcall sz-fn tree))
- (amt-list (loop for i in children
- for old-sz = (funcall sz-fn i)
- collect (truncate (/ (* amount old-sz) total))))
- (remainder (- amount (apply '+ amt-list)))
- (ofs 0))
- ;; spread the remainder out as evenly as possible
- (assert (< remainder (length amt-list)))
- (loop for i upfrom 0
- while (> remainder 0)
- do
- (incf (nth i amt-list))
- (decf remainder))
- ;; resize proportionally
- (loop for i in children
- for amt in amt-list
- do
- (expand-tree i amt dir)
- (offset-tree-dir i ofs dir)
- (incf ofs amt)))))))
-
-(defun join-subtrees (tree leaf)
- "expand the children of tree to occupy the space of
-LEAF. Return tree with leaf removed."
- (let* ((others (remove leaf tree))
- (newtree (if (= (length others) 1)
- (car others)
- others))
- (split-type (tree-split-type tree))
- (dir (if (eq split-type :column) :right :bottom))
- (ofsdir (if (eq split-type :column) :left :top))
- (amt (if (eq split-type :column)
- (tree-width leaf)
- (tree-height leaf)))
- (after (cdr (member leaf tree))))
- ;; align all children after the leaf with the edge of the
- ;; frame before leaf.
- (offset-tree-dir after amt ofsdir)
- (expand-tree newtree amt dir)
- newtree))
-
-(defun resize-tree (tree w h &optional x y)
- "Scale TREE to width W and height H, ignoring aspect. If X and Y are
- provided, reposition the TREE as well."
- (let* ((tw (tree-width tree))
- (th (tree-height tree))
- (wf (/ 1 (/ tw w)))
- (hf (/ 1 (/ th h)))
- (xo (if x (- x (tree-x tree)) 0))
- (yo (if y (- y (tree-y tree)) 0)))
- (tree-iterate tree (lambda (f)
- (setf (frame-height f) (round (* (frame-height f) hf))
- (frame-y f) (round (* (frame-y f) hf))
- (frame-width f) (round (* (frame-width f) wf))
- (frame-x f) (round (* (frame-x f) wf)))
- (incf (frame-y f) yo)
- (incf (frame-x f) xo)))
- (dformat 4 "resize-tree ~Dx~D -> ~Dx~D~%" tw th (tree-width tree) (tree-height tree))))
-
-(defun remove-frame (tree leaf)
- "Return a new tree with LEAF and it's sibling merged into
-one."
- (cond ((atom tree) tree)
- ((find leaf tree)
- (join-subtrees tree leaf))
- (t (mapcar (lambda (sib)
- (remove-frame sib leaf))
- tree))))
-
-(defun sync-frame-windows (group frame)
- "synchronize windows attached to FRAME."
- (mapc (lambda (w)
- (when (eq (window-frame w) frame)
- (dformat 3 "maximizing ~S~%" w)
- (maximize-window w)))
- (group-windows group)))
-
-(defun sync-all-frame-windows (group)
- "synchronize all frames in GROUP."
- (let ((tree (tile-group-frame-tree group)))
- (tree-iterate tree
- (lambda (f)
- (sync-frame-windows group f)))))
-
-(defun sync-head-frame-windows (group head)
- "synchronize all frames in GROUP and HEAD."
- (dolist (f (head-frames group head))
- (sync-frame-windows group f)))
-
-(defun offset-frames (group x y)
- "move the screen's frames around."
- (let ((tree (tile-group-frame-tree group)))
- (tree-iterate tree (lambda (frame)
- (incf (frame-x frame) x)
- (incf (frame-y frame) y)))))
-
-(defun resize-frame (group frame amount dim)
- "Resize FRAME by AMOUNT in DIM dimension, DIM can be
-either :width or :height"
- (check-type group group)
- (check-type frame frame)
- (check-type amount integer)
- ;; (check-type dim (member :width :height))
- (labels ((max-amount (parent node min dim-fn)
- (dformat 10 "max ~@{~a~^ ~}~%" parent node min dim-fn)
- (if parent
- (- (funcall dim-fn parent)
- (funcall dim-fn node)
- (* min (1- (length parent))))
- ;; no parent means the frame can't get any bigger.
- 0)))
- (let* ((tree (tile-group-frame-tree group))
- (parent (tree-parent tree frame))
- (gparent (tree-parent tree parent))
- (split-type (tree-split-type parent)))
- (dformat 10 "~s ~s parent: ~s ~s width: ~s h: ~s~%" dim amount split-type parent (tree-width parent) (tree-height parent))
- ;; normalize amount
- (let* ((max (ecase dim
- (:width
- (if (>= (frame-width frame) (frame-width (frame-head group frame)))
- 0
- (if (eq split-type :column)
- (max-amount parent frame *min-frame-width* 'tree-width)
- (max-amount gparent parent *min-frame-width* 'tree-width))))
- (:height
- (if (>= (frame-height frame) (frame-height (frame-head group frame)))
- 0
- (if (eq split-type :row)
- (max-amount parent frame *min-frame-height* 'tree-height)
- (max-amount gparent parent *min-frame-height* 'tree-height))))))
- (min (ecase dim
- ;; Frames taking up the entire HEAD in one
- ;; dimension can't be resized in that dimension.
- (:width
- (if (and (eq split-type :row)
- (or (null gparent)
- (>= (frame-width frame) (frame-width (frame-head group frame)))))
- 0
- (- *min-frame-width* (frame-width frame))))
- (:height
- (if (and (eq split-type :column)
- (or (null gparent)
- (>= (frame-height frame) (frame-height (frame-head group frame)))))
- 0
- (- *min-frame-height* (frame-height frame)))))))
- (setf amount (max (min amount max) min))
- (dformat 10 "bounds ~d ~d ~d~%" amount max min))
- ;; if FRAME is taking up the whole DIM or if AMOUNT = 0, do nothing
- (unless (zerop amount)
- (let* ((resize-parent (or (and (eq split-type :column)
- (eq dim :height))
- (and (eq split-type :row)
- (eq dim :width))))
- (to-resize (if resize-parent parent frame))
- (to-resize-parent (if resize-parent gparent parent))
- (lastp (= (position to-resize to-resize-parent) (1- (length to-resize-parent))))
- (to-shrink (if lastp
- (prev-sibling to-resize-parent to-resize)
- (next-sibling to-resize-parent to-resize))))
- (expand-tree to-resize amount (ecase dim
- (:width (if lastp :left :right))
- (:height (if lastp :top :bottom))))
- (expand-tree to-shrink (- amount) (ecase dim
- (:width (if lastp :right :left))
- (:height (if lastp :bottom :top))))
- (unless (and *resize-hides-windows* (eq *top-map* *resize-map*))
- (tree-iterate to-resize
- (lambda (leaf)
- (sync-frame-windows group leaf)))
- (tree-iterate to-shrink
- (lambda (leaf)
- (sync-frame-windows group leaf)))))))))
-
-(defun balance-frames-internal (group tree)
- "Resize all the children of tree to be of equal width or height
-depending on the tree's split direction."
- (let* ((split-type (tree-split-type tree))
- (fn (if (eq split-type :column)
- 'tree-width
- 'tree-height))
- (side (if (eq split-type :column)
- :right
- :bottom))
- (total (funcall fn tree))
- size rem)
- (multiple-value-setq (size rem) (truncate total (length tree)))
- (loop
- for i in tree
- for j = rem then (1- j)
- for totalofs = 0 then (+ totalofs ofs)
- for ofs = (+ (- size (funcall fn i)) (if (plusp j) 1 0))
- do
- (expand-tree i ofs side)
- (offset-tree-dir i totalofs side)
- (tree-iterate i (lambda (leaf)
- (sync-frame-windows group leaf))))))
-
-(defun split-frame (group how)
- "split the current frame into 2 frames. return T if it succeeded. NIL otherwise."
- (check-type how (member :row :column))
- (let* ((frame (tile-group-current-frame group))
- (head (frame-head group frame)))
- ;; don't create frames smaller than the minimum size
- (when (or (and (eq how :row)
- (>= (frame-height frame) (* *min-frame-height* 2)))
- (and (eq how :column)
- (>= (frame-width frame) (* *min-frame-width* 2))))
- (multiple-value-bind (f1 f2) (funcall (if (eq how :column)
- 'split-frame-h
- 'split-frame-v)
- group frame)
- (setf (tile-group-frame-head group head)
- (if (atom (tile-group-frame-head group head))
- (list f1 f2)
- (funcall-on-node (tile-group-frame-head group head)
- (lambda (tree)
- (if (eq (tree-split-type tree) how)
- (list-splice-replace frame tree f1 f2)
- (substitute (list f1 f2) frame tree)))
- (lambda (tree)
- (unless (atom tree)
- (find frame tree))))))
- (migrate-frame-windows group frame f1)
- (choose-new-frame-window f2 group)
- (if (eq (tile-group-current-frame group)
- frame)
- (setf (tile-group-current-frame group) f1))
- (setf (tile-group-last-frame group) f2)
- (sync-frame-windows group f1)
- (sync-frame-windows group f2)
- ;; we also need to show the new window in the other frame
- (when (frame-window f2)
- (unhide-window (frame-window f2)))
- t))))
-
-(defun draw-frame-outline (group f tl br)
- "Draw an outline around FRAME."
- (let* ((screen (group-screen group))
- (win (if (frame-window f) (window-xwin (frame-window f)) (screen-root screen)))
- (width (screen-frame-outline-width screen))
- (gc (screen-frame-outline-gc screen))
- (halfwidth (/ width 2)))
- (let ((x (frame-x f))
- (y (frame-display-y group f))
- (w (frame-width f))
- (h (frame-display-height group f)))
- (when tl
- (xlib:draw-line win gc
- x (+ halfwidth y) w 0 t)
- (xlib:draw-line win gc
- (+ halfwidth x) y 0 h t))
- (when br
- (xlib:draw-line win gc
- (+ x (- w halfwidth)) y 0 h t)
- (xlib:draw-line win gc
- x (+ y (- h halfwidth)) w 0 t)))))
-
-(defun draw-frame-outlines (group &optional head)
- "Draw an outline around all frames in GROUP."
- (clear-frame-outlines group)
- (dolist (h (if head (list head) (group-heads group)))
- (draw-frame-outline group h nil t)
- (tree-iterate (tile-group-frame-head group h) (lambda (f)
- (draw-frame-outline group f t nil)))))
-
-(defun clear-frame-outlines (group)
- "Clear the outlines drawn with DRAW-FRAME-OUTLINES."
- (xlib:clear-area (screen-root (group-screen group))))
-
-(defun draw-frame-numbers (group)
- "Draw the number of each frame in its corner. Return the list of
-windows used to draw the numbers in. The caller must destroy them."
- (let ((screen (group-screen group)))
- (mapcar (lambda (f)
- (let ((w (xlib:create-window
- :parent (screen-root screen)
- :x (frame-x f) :y (frame-display-y group f) :width 1 :height 1
- :background (screen-fg-color screen)
- :border (screen-border-color screen)
- :border-width 1
- :event-mask '())))
- (xlib:map-window w)
- (setf (xlib:window-priority w) :above)
- (echo-in-window w (screen-font screen)
- (screen-fg-color screen)
- (screen-bg-color screen)
- (string (get-frame-number-translation f)))
- (xlib:display-finish-output *display*)
- (dformat 3 "mapped ~S~%" (frame-number f))
- w))
- (group-frames group))))
-
-
-;;; Screen functions
-
-(defun netwm-update-client-list-stacking (screen)
- (unless *initializing*
- (xlib:change-property (screen-root screen)
- :_NET_CLIENT_LIST_STACKING
- ;; Order is bottom to top.
- (reverse (mapcar 'window-xwin (all-windows)))
- :window 32
- :transform #'xlib:drawable-id
- :mode :replace)))
-
-(defun netwm-update-client-list (screen)
- (xlib:change-property (screen-root screen)
- :_NET_CLIENT_LIST
- (screen-mapped-windows screen)
- :window 32
- :transform #'xlib:drawable-id
- :mode :replace)
- (netwm-update-client-list-stacking screen))
-
-
-(defun screen-add-mapped-window (screen xwin)
- (push xwin (screen-mapped-windows screen))
- (netwm-update-client-list screen))
-
-(defun screen-remove-mapped-window (screen xwin)
- (unregister-window xwin)
- (setf (screen-mapped-windows screen)
- (remove xwin (screen-mapped-windows screen)))
- (netwm-update-client-list screen))
-
-(defun sort-screens ()
- "Return the list of screen sorted by ID."
- (sort1 *screen-list* '< :key 'screen-id))
-
-(defun next-screen (&optional (list (sort-screens)))
- (let ((matches (member (current-screen) list)))
- (if (null (cdr matches))
- ;; If the last one in the list is current, then
- ;; use the first one.
- (car list)
- ;; Otherwise, use the next one in the list.
- (cadr matches))))
-
-(defun move-screen-to-head (screen)
- (setf *screen-list* (remove screen *screen-list*))
- (push screen *screen-list*))
-
-(defun switch-to-screen (screen)
- (when (and screen
- (not (eq screen (current-screen))))
- (if (screen-focus screen)
- (xlib:set-input-focus *display* (window-xwin (screen-focus screen)) :POINTER-ROOT)
- (xlib:set-input-focus *display* (screen-focus-window screen) :POINTER-ROOT))
- (move-screen-to-head screen)))
-
-(defun screen-set-focus (screen window)
- (when (eq (window-group window)
- (screen-current-group screen))
- ;;(format t "FOCUS TO: ~a ~a~%" window (window-xwin window))
- ;;(format t "FOCUS BEFORE: ~a~%" (multiple-value-list (xlib:input-focus *display*)))
- ;;(format t "FOCUS RET: ~a~%" (xlib:set-input-focus *display* (window-xwin window) :POINTER-ROOT))
- (xlib:set-input-focus *display* (window-xwin window) :POINTER-ROOT)
- ;;(xlib:display-finish-output *display*)
- ;;(format t "FOCUS IS: ~a~%" (multiple-value-list (xlib:input-focus *display*)))
- (xlib:change-property (screen-root screen) :_NET_ACTIVE_WINDOW
- (list (window-xwin window))
- :window 32
- :transform #'xlib:drawable-id
- :mode :replace)
- (setf (screen-focus screen) window)
- (move-screen-to-head screen)))
-
-(defun screen-current-window (screen)
- "Return the current window on the specified screen"
- (group-current-window (screen-current-group screen)))
-
-(defun current-window ()
- "Return the current window on the current screen"
- (screen-current-window (current-screen)))
-
-(defun register-window (window)
- (setf (gethash (xlib:window-id (window-xwin window)) *xwin-to-window*) window))
-
-(defun unregister-window (xwin)
- (remhash (xlib:window-id xwin) *xwin-to-window*))
-
-(defun window-by-id (id)
- (gethash id *xwin-to-window*))
-
-(defun find-window (xwin)
- (window-by-id (xlib:window-id xwin)))
-
-(defun find-window-by-parent (xwin &optional (windows (all-windows)))
- (dformat 3 "find-window-by-parent!~%")
- (find xwin windows :key 'window-parent :test 'xlib:window-equal))
-
-(defun screen-root (screen)
- (xlib:screen-root (screen-number screen)))
-
-(defun update-colors-for-screen (screen)
- (let ((fg (screen-fg-color screen))
- (bg (screen-bg-color screen)))
- (setf (xlib:gcontext-foreground (screen-message-gc screen)) fg
- (xlib:gcontext-background (screen-message-gc screen)) bg
- (xlib:gcontext-foreground (screen-frame-outline-gc screen)) fg
- (xlib:gcontext-background (screen-frame-outline-gc screen)) bg
- (ccontext-default-fg (screen-message-cc screen)) fg
- (ccontext-default-bg (screen-message-cc screen)) bg))
- (dolist (i (list (screen-message-window screen)
- (screen-input-window screen)))
- (setf (xlib:window-border i) (screen-border-color screen)
- (xlib:window-background i) (screen-bg-color screen)))
- ;; update the backgrounds of all the managed windows
- (dolist (g (screen-groups screen))
- (dolist (w (group-windows g))
- (unless (eq w (group-current-window g))
- (setf (xlib:window-background (window-parent w)) (screen-win-bg-color screen))
- (xlib:clear-area (window-parent w)))))
- (dolist (i (screen-withdrawn-windows screen))
- (setf (xlib:window-background (window-parent i)) (screen-win-bg-color screen))
- (xlib:clear-area (window-parent i)))
- (update-screen-color-context screen))
-
-(defun update-colors-all-screens ()
- "After setting the fg, bg, or border colors. call this to sync any existing windows."
- (mapc 'update-colors-for-screen *screen-list*))
-
-(defun update-border-for-screen (screen)
- (setf (xlib:drawable-border-width (screen-input-window screen)) (screen-msg-border-width screen)
- (xlib:drawable-border-width (screen-message-window screen)) (screen-msg-border-width screen)))
-
-(defun update-border-all-screens ()
- "After setting the border width call this to sync any existing windows."
- (mapc 'update-border-for-screen *screen-list*))
-
-(defun internal-window-p (screen win)
- "Return t if win is a window used by stumpwm"
- (or (xlib:window-equal (screen-message-window screen) win)
- (xlib:window-equal (screen-input-window screen) win)
- (xlib:window-equal (screen-focus-window screen) win)
- (xlib:window-equal (screen-key-window screen) win)))
-
-(defun unmap-message-window (screen)
- "Unmap the screen's message window, if it is mapped."
- (unless (eq (xlib:window-map-state (screen-message-window screen)) :unmapped)
- (xlib:unmap-window (screen-message-window screen))))
-
-(defun unmap-all-message-windows ()
- (mapc #'unmap-message-window *screen-list*)
- (when (timer-p *message-window-timer*)
- (cancel-timer *message-window-timer*)
- (setf *message-window-timer* nil)))
-
-(defun unmap-frame-indicator-window (screen)
- "Unmap the screen's message window, if it is mapped."
-;; (unless (eq (xlib:window-map-state (screen-frame-window screen)) :unmapped)
- (xlib:unmap-window (screen-frame-window screen)))
-
-(defun unmap-all-frame-indicator-windows ()
- (mapc #'unmap-frame-indicator-window *screen-list*)
- (when (timer-p *frame-indicator-timer*)
- (cancel-timer *frame-indicator-timer*)
- (setf *frame-indicator-timer* nil)))
-
-(defun reset-message-window-timer ()
- "Set the message window timer to timeout in *timeout-wait* seconds."
- (unless *ignore-echo-timeout*
- (when (timer-p *message-window-timer*)
- (cancel-timer *message-window-timer*))
- (setf *message-window-timer* (run-with-timer *timeout-wait* nil
- 'unmap-all-message-windows))))
-
-(defun reset-frame-indicator-timer ()
- "Set the message window timer to timeout in *timeout-wait* seconds."
- (when (timer-p *frame-indicator-timer*)
- (cancel-timer *frame-indicator-timer*))
- (setf *frame-indicator-timer* (run-with-timer *timeout-frame-indicator-wait* nil
- 'unmap-all-frame-indicator-windows)))
-
-(defun show-frame-outline (group &optional (clear t))
- ;; Don't draw if this isn't a current group!
- (when (find group (mapcar 'screen-current-group *screen-list*))
- (dformat 5 "show-frame-outline!~%")
- ;; *resize-hides-windows* uses the frame outlines for display,
- ;; so try not to interfere.
- (unless (eq *top-map* *resize-map*)
- (when clear
- (clear-frame-outlines group))
- (let ((frame (tile-group-current-frame group)))
- (unless (and (= 1 (length (tile-group-frame-tree group)))
- (atom (first (tile-group-frame-tree group))))
- ;; draw the outline
- (unless (frame-window frame)
- (draw-frame-outline group frame t t)))))))
-
-(defun show-frame-indicator (group &optional force)
- (show-frame-outline group)
- ;; FIXME: Arg, these tests are already done in show-frame-outline
- (when (find group (mapcar 'screen-current-group *screen-list*))
- (when (or force
- (and (or (> 1 (length (tile-group-frame-tree group)))
- (not (atom (first (tile-group-frame-tree group)))))
- (not *suppress-frame-indicator*)))
- (let ((frame (tile-group-current-frame group))
- (w (screen-frame-window (current-screen)))
- (string *frame-indicator-text*)
- (font (screen-font (current-screen))))
- (xlib:with-state (w)
- (setf (xlib:drawable-x w) (+ (frame-x frame)
- (truncate (- (frame-width frame) (xlib:text-width font string)) 2))
- (xlib:drawable-y w) (+ (frame-y frame)
- (truncate (- (frame-height frame) (font-height font)) 2))
- (xlib:window-priority w) :above))
- (xlib:map-window w)
- (echo-in-window w font (screen-fg-color (current-screen)) (screen-bg-color (current-screen)) string)
- (reset-frame-indicator-timer)))))
-
-(defun echo-in-window (win font fg bg string)
- (let* ((height (font-height font))
- (gcontext (xlib:create-gcontext :drawable win
- :font font
- :foreground fg
- :background bg))
- (width (xlib:text-width font string)))
- (xlib:with-state (win)
- (setf (xlib:drawable-height win) height
- (xlib:drawable-width win) width))
- (xlib:clear-area win)
- (xlib:display-finish-output *display*)
- (xlib:draw-image-glyphs win gcontext 0 (xlib:font-ascent font) string)))
-
-(defun push-last-message (screen strings highlights)
- ;; only push unique messages
- (unless *record-last-msg-override*
- (push strings (screen-last-msg screen))
- (push highlights (screen-last-msg-highlights screen))
- ;; crop for size
- (when (>= (length (screen-last-msg screen)) *max-last-message-size*)
- (setf (screen-last-msg screen) (butlast (screen-last-msg screen)))
- (setf (screen-last-msg-highlights screen) (butlast (screen-last-msg-highlights screen))))))
-
-(defun redraw-current-message (screen)
- (let ((*record-last-msg-override* t)
- (*ignore-echo-timeout* t))
- (dformat 5 "Redrawing message window!~%")
- (apply 'echo-string-list screen (screen-current-msg screen) (screen-current-msg-highlights screen))))
-
-(defun echo-nth-last-message (screen n)
- (let ((*record-last-msg-override* t))
- (apply 'echo-string-list screen (nth n (screen-last-msg screen)) (nth n (screen-last-msg-highlights screen)))))
-
-(defun echo-string-list (screen strings &rest highlights)
- "Draw each string in l in the screen's message window. HIGHLIGHT is
- the nth entry to highlight."
- (when strings
- (unless *executing-stumpwm-command*
- (let ((width (render-strings screen (screen-message-cc screen) *message-window-padding* 0 strings '() nil)))
- (setup-message-window screen (length strings) width)
- (render-strings screen (screen-message-cc screen) *message-window-padding* 0 strings highlights))
- (setf (screen-current-msg screen)
- strings
- (screen-current-msg-highlights screen)
- highlights))
- (push-last-message screen strings highlights)
- (xlib:display-finish-output *display*)
- ;; Set a timer to hide the message after a number of seconds
- (if *suppress-echo-timeout*
- ;; any left over timers need to be canceled.
- (when (timer-p *message-window-timer*)
- (cancel-timer *message-window-timer*)
- (setf *message-window-timer* nil))
- (reset-message-window-timer))
- (apply 'run-hook-with-args *message-hook* strings)))
-
-(defun echo-string (screen msg)
- "Display @var{string} in the message bar on @var{screen}. You almost always want to use @command{message}."
- (echo-string-list screen (split-string msg (string #\Newline))))
-
-(defun message (fmt &rest args)
- "run FMT and ARGS through `format' and echo the result to the current screen."
- (echo-string (current-screen) (apply 'format nil fmt args)))
-
-
-(defun err (fmt &rest args)
- "run FMT and ARGS through format and echo the result to the
-current screen along with a backtrace. For careful study, the
-message does not time out."
- (let ((*suppress-echo-timeout* t))
- (echo-string (current-screen)
- (concat (apply 'format nil fmt args)
- (backtrace-string)))))
-
-(defun message-no-timeout (fmt &rest args)
- "Like message, but the window doesn't disappear after a few seconds."
- (let ((*suppress-echo-timeout* t))
- (apply 'message fmt args)))
-
-(defmacro with-current-screen (screen &body body)
- "A macro to help us out with early set up."
- `(let ((*screen-list* (list ,screen)))
- ,@body))
-
-(defun current-screen ()
- "Return the current screen."
- (car *screen-list*))
-
-(defun netwm-set-properties (screen focus-window)
- "Set NETWM properties on the root window of the specified screen.
-FOCUS-WINDOW is an extra window used for _NET_SUPPORTING_WM_CHECK."
- (let* ((screen-number (screen-number screen))
- (root (xlib:screen-root screen-number)))
- ;; _NET_SUPPORTED
- (xlib:change-property root :_NET_SUPPORTED
- (mapcar (lambda (a)
- (xlib:intern-atom *display* a))
- (append +netwm-supported+
- (mapcar #'car +netwm-window-types+)))
- :atom 32)
-
- ;; _NET_SUPPORTING_WM_CHECK
- (xlib:change-property root :_NET_SUPPORTING_WM_CHECK
- (list focus-window) :window 32
- :transform #'xlib:drawable-id)
- (xlib:change-property focus-window :_NET_SUPPORTING_WM_CHECK
- (list focus-window) :window 32
- :transform #'xlib:drawable-id)
- (xlib:change-property focus-window :_NET_WM_NAME
- "stumpwm"
- :string 8 :transform #'xlib:char->card8)
-
- ;; _NET_CLIENT_LIST
- (xlib:change-property root :_NET_CLIENT_LIST
- () :window 32
- :transform #'xlib:drawable-id)
-
- ;; _NET_DESKTOP_GEOMETRY
- (xlib:change-property root :_NET_DESKTOP_GEOMETRY
- (list (xlib:screen-width screen-number)
- (xlib:screen-height screen-number))
- :cardinal 32)
-
- ;; _NET_DESKTOP_VIEWPORT
- (xlib:change-property root :_NET_DESKTOP_VIEWPORT
- (list 0 0) :cardinal 32)
-
- (netwm-set-group-properties screen)))
-
-(defun init-screen (screen-number id host)
- "Given a screen number, returns a screen structure with initialized members"
- ;; Listen for the window manager events on the root window
- (dformat 1 "Initializing screen: ~a ~a~%" host id)
- (setf (xlib:window-event-mask (xlib:screen-root screen-number))
- '(:substructure-redirect
- :substructure-notify
- :property-change
- :structure-notify
- :button-press
- :exposure))
- (xlib:display-finish-output *display*)
- ;; Initialize the screen structure
- (labels ((ac (color)
- (xlib:alloc-color (xlib:screen-default-colormap screen-number) color)))
- (let* ((screen (make-screen))
- (fg (ac +default-foreground-color+))
- (bg (ac +default-background-color+))
- (border (ac +default-border-color+))
- (focus (ac +default-focus-color+))
- (unfocus (ac +default-unfocus-color+))
- (win-bg (ac +default-window-background-color+))
- (input-window (xlib:create-window :parent (xlib:screen-root screen-number)
- :x 0 :y 0 :width 20 :height 20
- :background bg
- :border border
- :border-width 1
- :colormap (xlib:screen-default-colormap
- screen-number)
- :event-mask '(:key-press :key-release)))
- (focus-window (xlib:create-window :parent (xlib:screen-root screen-number)
- :x 0 :y 0 :width 1 :height 1))
- (key-window (xlib:create-window :parent (xlib:screen-root screen-number)
- :x 0 :y 0 :width 1 :height 1
- :event-mask '(:key-press :key-release)))
- (message-window (xlib:create-window :parent (xlib:screen-root screen-number)
- :x 0 :y 0 :width 1 :height 1
- :background bg
- :bit-gravity :north-east
- :border border
- :border-width 1
- :colormap (xlib:screen-default-colormap
- screen-number)
- :event-mask '(:exposure)))
- (frame-window (xlib:create-window :parent (xlib:screen-root screen-number)
- :x 0 :y 0 :width 20 :height 20
- :background bg
- :border border
- :border-width 1
- :colormap (xlib:screen-default-colormap
- screen-number)
- :event-mask '(:exposure)))
- (font (xlib:open-font *display* +default-font-name+))
- (group (make-tile-group
- :screen screen
- :number 1
- :name *default-group-name*)))
- ;; Create our screen structure
- ;; The focus window is mapped at all times
- (xlib:map-window focus-window)
- (xlib:map-window key-window)
- (xwin-grab-keys focus-window)
- (setf (screen-number screen) screen-number
- (screen-id screen) id
- (screen-host screen) host
- (screen-groups screen) (list group)
- (screen-current-group screen) group
- (screen-font screen) font
- (screen-fg-color screen) fg
- (screen-bg-color screen) bg
- (screen-win-bg-color screen) win-bg
- (screen-border-color screen) border
- (screen-focus-color screen) focus
- (screen-unfocus-color screen) unfocus
- (screen-msg-border-width screen) 1
- (screen-frame-outline-width screen) +default-frame-outline-width+
- (screen-input-window screen) input-window
- (screen-focus-window screen) focus-window
- (screen-key-window screen) key-window
- (screen-frame-window screen) frame-window
- (screen-ignore-msg-expose screen) 0
- (screen-message-cc screen) (make-ccontext :win message-window
- :gc (xlib:create-gcontext
- :drawable message-window
- :font font
- :foreground fg
- :background bg))
- (screen-frame-outline-gc screen) (xlib:create-gcontext :drawable (screen-root screen)
- :font font
- :foreground fg
- :background fg
- :line-style :double-dash
- :line-width +default-frame-outline-width+))
- (setf (screen-heads screen) (make-screen-heads screen (xlib:screen-root screen-number))
- (tile-group-frame-tree group) (copy-heads screen)
- (tile-group-current-frame group) (first (tile-group-frame-tree group)))
- (netwm-set-properties screen focus-window)
- (update-colors-for-screen screen)
- (update-color-map screen)
- screen)))
-
-
-;;; Head functions
-
-(defun head-by-number (screen n)
- (find n (screen-heads screen) :key 'head-number))
-
-(defun parse-xinerama-head (line)
- (ppcre:register-groups-bind (('parse-integer number width height x y))
- ("^ +head #([0-9]+): ([0-9]+)x([0-9]+) @ ([0-9]+),([0-9]+)" line :sharedp t)
- (handler-case
- (make-head :number number
- :x x :y y
- :width width
- :height height)
- (parse-error ()
- nil))))
-
-(defun make-screen-heads (screen root)
- "or use xdpyinfo to query the xinerama extension, if it's enabled."
- (or (and (xlib:query-extension *display* "XINERAMA")
- (with-current-screen screen
- ;; Ignore 'clone' heads.
- (loop
- for i = 0 then (1+ i)
- for h in
- (delete-duplicates
- (loop for i in (split-string (run-shell-command "xdpyinfo -ext XINERAMA" t))
- for head = (parse-xinerama-head i)
- when head
- collect head)
- :test #'frames-overlap-p)
- do (setf (head-number h) i)
- collect h)))
- (list (make-head :number 0
- :x 0 :y 0
- :width (xlib:drawable-width root)
- :height (xlib:drawable-height root)
- :window nil))))
-
-(defun copy-heads (screen)
- "Return a copy of screen's heads."
- (mapcar 'copy-frame (screen-heads screen)))
-
-
-;; Determining a frame's head based on position probably won't
-;; work with overlapping heads. Would it be better to walk
-;; up the frame tree?
-(defun frame-head (group frame)
- (dolist (head (screen-heads (group-screen group)))
- (when (and
- (>= (frame-x frame) (frame-x head))
- (>= (frame-y frame) (frame-y head))
- (<= (+ (frame-x frame) (frame-width frame))
- (+ (frame-x head) (frame-width head)))
- (<= (+ (frame-y frame) (frame-height frame))
- (+ (frame-y head) (frame-height head))))
- (return head))))
-
-(defun group-heads (group)
- (screen-heads (group-screen group)))
-
-(defun tile-group-frame-head (group head)
- (elt (tile-group-frame-tree group) (position head (group-heads group))))
-
-(defun (setf tile-group-frame-head) (frame group head)
- (setf (elt (tile-group-frame-tree group) (position head (group-heads group))) frame))
-
-(defun current-head (&optional (group (current-group)))
- (frame-head group (tile-group-current-frame group)))
-
-(defun head-windows (group head)
- "Returns a list of windows on HEAD of GROUP"
- (remove-if-not
- (lambda (w)
- (eq head (frame-head group (window-frame w))))
- (group-windows group)))
-
-(defun frame-is-head (group frame)
- (< (frame-number frame) (length (group-heads group))))
-
-(defun add-head (screen head)
- (dformat 1 "Adding head #~D~%" (head-number head))
- (setf (screen-heads screen) (sort (push head (screen-heads screen)) #'< :key 'head-number))
- (dolist (group (screen-groups screen))
- (setf (tile-group-frame-tree group)
- (sort (push (copy-frame head) (tile-group-frame-tree group))
- #'< :key (lambda (tile)
- (if (atom tile)
- (frame-number tile)
- (frame-number (car tile))))))
- ;; Try to put something in the new frame
- (let ((frame (tile-group-frame-head group head)))
- (choose-new-frame-window frame group)
- (when (frame-window frame)
- (unhide-window (frame-window frame))))))
-
-(defun remove-head (screen head)
- (dformat 1 "Removing head #~D~%" (head-number head))
- (dolist (group (screen-groups screen))
- ;; Hide its windows.
- (let ((windows (head-windows group head)))
- ;; Remove it from the frame tree.
- (setf (tile-group-frame-tree group) (delete (tile-group-frame-head group head) (tile-group-frame-tree group)))
- ;; Just set current frame to whatever.
- (let ((frame (first (group-frames group))))
- (setf (tile-group-current-frame group) frame
- (tile-group-last-frame group) nil)
- (dolist (window windows)
- (hide-window window)
- (setf (window-frame window) frame))))
- ;; Try to do something with the orphaned windows
- (populate-frames group))
- (when (head-mode-line head)
- (toggle-mode-line screen head))
- ;; Remove it from SCREEN's head list.
- (setf (screen-heads screen) (delete head (screen-heads screen))))
-
-(defun scale-head (screen oh nh)
- "Scales head OH to match the dimensions of NH."
- (dolist (group (screen-groups screen))
- (resize-tree (tile-group-frame-head group oh) (head-width nh) (head-height nh) (head-x nh) (head-y nh)))
- (setf (head-x oh) (head-x nh)
- (head-y oh) (head-y nh)
- (head-width oh) (head-width nh)
- (head-height oh) (head-height nh)))
-
;;; keyboard helper functions
@@ -2813,692 +135,3 @@ list of modifier symbols."
(defun warp-pointer-relative (dx dy)
"Move the pointer by DX and DY relative to the current location."
(xlib:warp-pointer-relative *display* dx dy))
-
-
-;; Event handler functions
-
-(defparameter *event-fn-table* (make-hash-table)
- "A hash of event types to functions")
-
-(defmacro define-stump-event-handler (event keys &body body)
- (let ((fn-name (gensym))
- (event-slots (gensym)))
- `(labels ((,fn-name (&rest ,event-slots &key ,@keys &allow-other-keys)
- (declare (ignore ,event-slots))
- ,@body))
- (setf (gethash ,event *event-fn-table*) #',fn-name))))
-
- ;(define-stump-event-handler :map-notify (event-window window override-redirect-p)
- ; )
-
-(defun handle-mode-line-window (xwin x y width height)
- (declare (ignore width))
- (let ((ml (find-mode-line-window xwin)))
- (when ml
- (setf (xlib:drawable-height xwin) height)
- (update-mode-line-position ml x y)
- (resize-mode-line ml)
- (sync-mode-line ml))))
-
-(defun handle-unmanaged-window (xwin x y width height border-width value-mask)
- "Call this function for windows that stumpwm isn't
- managing. Basically just give the window what it wants."
- (labels ((has-x (mask) (= 1 (logand mask 1)))
- (has-y (mask) (= 2 (logand mask 2)))
- (has-w (mask) (= 4 (logand mask 4)))
- (has-h (mask) (= 8 (logand mask 8)))
- (has-bw (mask) (= 16 (logand mask 16)))
- ;; (has-stackmode (mask) (= 64 (logand mask 64)))
- )
- (xlib:with-state (xwin)
- (when (has-x value-mask)
- (setf (xlib:drawable-x xwin) x))
- (when (has-y value-mask)
- (setf (xlib:drawable-y xwin) y))
- (when (has-h value-mask)
- (setf (xlib:drawable-height xwin) height))
- (when (has-w value-mask)
- (setf (xlib:drawable-width xwin) width))
- (when (has-bw value-mask)
- (setf (xlib:drawable-border-width xwin) border-width)))))
-
-(defun update-configuration (win)
- ;; Send a synthetic configure-notify event so that the window
- ;; knows where it is onscreen.
- (xwin-send-configuration-notify (window-xwin win)
- (xlib:drawable-x (window-parent win))
- (xlib:drawable-y (window-parent win))
- (window-width win) (window-height win) 0))
-
-(defun handle-managed-window (window width height stack-mode value-mask)
- "This is a managed window so deal with it appropriately."
- ;; Grant the stack-mode change (if it's mapped)
- (set-window-geometry window :width width :height height)
- (maximize-window window)
- (when (and (window-in-current-group-p window)
- ;; stack-mode change?
- (= 64 (logand value-mask 64)))
- (case stack-mode
- (:above
- (maybe-raise-window window))))
- (update-configuration window))
-
-(defun handle-window-move (win x y relative-to &optional (value-mask -1))
- (when *honor-window-moves*
- (dformat 3 "Window requested new position ~D,~D relative to ~S~%" x y relative-to)
- (labels ((has-x (mask) (= 1 (logand mask 1)))
- (has-y (mask) (= 2 (logand mask 2))))
- (when (or (eq relative-to :root) (has-x value-mask) (has-y value-mask))
- (let* ((group (window-group win))
- (pos (if (eq relative-to :parent)
- (list
- (+ (xlib:drawable-x (window-parent win)) x)
- (+ (xlib:drawable-y (window-parent win)) y))
- (list x y)))
- (frame (apply #'find-frame group pos)))
- (when frame
- (pull-window win frame)))))))
-
-(define-stump-event-handler :configure-request (stack-mode #|parent|# window #|above-sibling|# x y width height border-width value-mask)
- ;; Grant the configure request but then maximize the window after the granting.
- (dformat 3 "CONFIGURE REQUEST ~@{~S ~}~%" stack-mode window x y width height border-width value-mask)
- (let ((win (find-window window)))
- (cond
- (win
- (handle-window-move win x y :parent value-mask)
- (handle-managed-window win width height stack-mode value-mask))
- ((handle-mode-line-window window x y width height))
- (t (handle-unmanaged-window window x y width height border-width value-mask)))))
-
-(defun scale-screen (screen heads)
- "Scale all frames of all groups of SCREEN to match the dimensions
- of HEADS."
- (when (< (length heads) (length (screen-heads screen)))
- ;; Some heads were removed (or cloned), try to guess which.
- (dolist (oh (screen-heads screen))
- (dolist (nh heads)
- (when (and (= (head-x nh) (head-x oh))
- (= (head-y nh) (head-y oh)))
- ;; Same screen position; probably the same head.
- (setf (head-number nh) (head-number oh)))))
- ;; Actually remove the missing heads.
- (dolist (head (set-difference (screen-heads screen) heads :key 'head-number))
- (remove-head screen head)))
- (loop
- for nh in heads
- as oh = (find (head-number nh) (screen-heads screen) :key 'head-number)
- do (if oh
- (scale-head screen oh nh)
- (add-head screen nh))))
-
-(define-stump-event-handler :configure-notify (stack-mode #|parent|# window #|above-sibling|# x y width height border-width value-mask)
- (dformat 4 "CONFIGURE NOTIFY ~@{~S ~}~%" stack-mode window x y width height border-width value-mask)
- (let ((screen (find-screen window)))
- (when screen
- (let ((old-heads (copy-list (screen-heads screen))))
- (setf (screen-heads screen) nil)
- (let ((new-heads (make-screen-heads screen (screen-root screen))))
- (setf (screen-heads screen) old-heads)
- (cond
- ((equalp old-heads new-heads)
- (dformat 3 "Bogus configure-notify on root window of ~S~%" screen) t)
- (t
- (dformat 1 "Updating Xinerama configuration for ~S.~%" screen)
- (if new-heads
- (progn
- (scale-screen screen new-heads)
- (mapc 'sync-all-frame-windows (screen-groups screen))
- (update-mode-lines screen))
- (dformat 1 "Invalid configuration! ~S~%" new-heads)))))))))
-
-(define-stump-event-handler :map-request (parent send-event-p window)
- (unless send-event-p
- ;; This assumes parent is a root window and it should be.
- (dformat 3 "map request: ~a ~a ~a~%" window parent (find-window window))
- (let ((screen (find-screen parent))
- (win (find-window window))
- (wwin (find-withdrawn-window window)))
- ;; only absorb it if it's not already managed (it could be iconic)
- (cond
- (win (dformat 1 "map request for mapped window ~a~%" win))
- ((eq (xwin-type window) :dock)
- (when wwin
- (setf screen (window-screen wwin)))
- (dformat 1 "window is dock-type. attempting to place in mode-line.")
- (place-mode-line-window screen window)
- ;; Some panels are broken and only set the dock type after they map and withdraw.
- (when wwin
- (setf (screen-withdrawn-windows screen) (delete wwin (screen-withdrawn-windows screen))))
- t)
- (wwin (restore-window wwin))
- ((xlib:get-property window :_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR)
- ;; Do nothing if this is a systray window (the system tray
- ;; will handle it, if there is one, and, if there isn't the
- ;; user doesn't want this popping up as a managed window
- ;; anyway.
- t)
- (t
- (let ((window (process-mapped-window screen window)))
- ;; Give it focus
- (if (deny-request-p window *deny-map-request*)
- (unless *suppress-deny-messages*
- (if (eq (window-group window) (current-group))
- (echo-string (window-screen window) (format nil "'~a' denied map request" (window-name window)))
- (echo-string (window-screen window) (format nil "'~a' denied map request in group ~a" (window-name window) (group-name (window-group window))))))
- (frame-raise-window (window-group window) (window-frame window) window
- (if (eq (window-frame window)
- (tile-group-current-frame (window-group window)))
- t nil)))))))))
-
-(define-stump-event-handler :unmap-notify (send-event-p event-window window #|configure-p|#)
- ;; There are two kinds of unmap notify events: the straight up
- ;; ones where event-window and window are the same, and
- ;; substructure unmap events when the event-window is the parent
- ;; of window.
- (dformat 2 "UNMAP: ~s ~s ~a~%" send-event-p (not (xlib:window-equal event-window window)) (find-window window))
- (unless (and (not send-event-p)
- (not (xlib:window-equal event-window window)))
- (let ((window (find-window window)))
- ;; if we can't find the window then there's nothing we need to
- ;; do.
- (when window
- (if (plusp (window-unmap-ignores window))
- (progn
- (dformat 3 "decrement ignores! ~d~%" (window-unmap-ignores window))
- (decf (window-unmap-ignores window)))
- (withdraw-window window))))))
-
-;;(define-stump-event-handler :create-notify (#|window parent x y width height border-width|# override-redirect-p))
-;; (unless (or override-redirect-p
-;; (internal-window-p (window-screen window) window))
-;; (process-new-window (window-screen window) window))
-;; (run-hook-with-args *new-window-hook* window)))
-
-(define-stump-event-handler :destroy-notify (send-event-p event-window window)
- (unless (or send-event-p
- (xlib:window-equal event-window window))
- ;; Ignore structure destroy notifies and only
- ;; use substructure destroy notifiers. This way
- ;; event-window is the window's parent.
- (let ((win (or (find-window window)
- (find-withdrawn-window window))))
- (if win
- (destroy-window win)
- (progn
- (let ((ml (find-mode-line-window window)))
- (when ml (destroy-mode-line-window ml))))))))
-
-(defun read-from-keymap (kmap &optional update-fn)
- "Read a sequence of keys from the user, guided by the keymap,
-KMAP and return the binding or nil if the user hit an unbound sequence.
-
-The Caller is responsible for setting up the input focus."
- (let* ((code-state (read-key-no-modifiers))
- (code (car code-state))
- (state (cdr code-state)))
- (handle-keymap kmap code state nil nil update-fn)))
-
-(defun handle-keymap (kmap code state key-seq grab update-fn)
- "Find the command mapped to the (code state) and return it."
- ;; a symbol is assumed to have a hashtable as a value.
- (dformat 1 "Awaiting key ~a~%" kmap)
- (let ((keymap '()))
- (when (and (symbolp kmap)
- (boundp kmap)
- (hash-table-p (symbol-value kmap)))
- (setf
- keymap kmap
- kmap (symbol-value kmap)))
- (check-type kmap hash-table)
- (let* ((key (code-state->key code state))
- (cmd (lookup-key kmap key))
- (key-seq (cons key key-seq)))
- (dformat 1 "key-press: ~S ~S ~S~%" key state cmd)
- (run-hook-with-args *key-press-hook* key key-seq cmd)
- (when update-fn
- (funcall update-fn key-seq))
- (if cmd
- (cond
- ((or (hash-table-p cmd)
- (and (symbolp cmd)
- (boundp cmd)
- (hash-table-p (symbol-value cmd))))
- (when grab
- (grab-pointer (current-screen)))
- (let* ((code-state (read-key-no-modifiers))
- (code (car code-state))
- (state (cdr code-state)))
- (unwind-protect
- (handle-keymap cmd code state key-seq nil update-fn)
- (when grab (ungrab-pointer)))))
- (t (values cmd key-seq)))
- (if (find key (list (kbd "?")
- (kbd "C-h"))
- :test 'equalp)
- (progn (display-keybinding keymap) (values t key-seq))
- (values nil key-seq))))))
-
-(define-stump-event-handler :key-press (code state #|window|#)
- (labels ((get-cmd (code state)
- (with-focus (screen-key-window (current-screen))
- (handle-keymap *top-map* code state nil t nil))))
- (unwind-protect
- ;; modifiers can sneak in with a race condition. so avoid that.
- (unless (is-modifier code)
- (multiple-value-bind (cmd key-seq) (get-cmd code state)
- (cond
- ((eq cmd t))
- (cmd
- (unmap-message-window (current-screen))
- (interactive-command cmd) t)
- (t (message "~{~a ~}not bound." (mapcar 'print-key (nreverse key-seq))))))))))
-
-(defun bytes-to-window (bytes)
- "A sick hack to assemble 4 bytes into a 32 bit number. This is
-because ratpoison sends the rp_command_request window in 8 byte
-chunks."
- (+ (first bytes)
- (ash (second bytes) 8)
- (ash (third bytes) 16)
- (ash (fourth bytes) 24)))
-
-(defun handle-rp-commands (root)
- "Handle a ratpoison style command request."
- (labels ((one-cmd ()
- (multiple-value-bind (win type format bytes-after) (xlib:get-property root :rp_command_request :end 4 :delete-p t)
- (declare (ignore type format))
- (setf win (xlib::lookup-window *display* (bytes-to-window win)))
- (when (xlib:window-p win)
- (let* ((data (xlib:get-property win :rp_command))
- (interactive-p (car data))
- (cmd (map 'string 'code-char (nbutlast (cdr data)))))
- (declare (ignore interactive-p))
- (interactive-command cmd)
- (xlib:change-property win :rp_command_result (map 'list 'char-code "0TODO") :string 8)
- (xlib:display-finish-output *display*)))
- bytes-after)))
- (loop while (> (one-cmd) 0))))
-
-(defun handle-stumpwm-commands (root)
- "Handle a StumpWM style command request."
- (let* ((win root)
- (screen (find-screen root))
- (data (xlib:get-property win :stumpwm_command :delete-p t))
- (cmd (bytes-to-string data)))
- (let ((msgs (screen-last-msg screen))
- (hlts (screen-last-msg-highlights screen))
- (*executing-stumpwm-command* t))
- (setf (screen-last-msg screen) '()
- (screen-last-msg-highlights screen) '())
- (interactive-command cmd)
- (xlib:change-property win :stumpwm_command_result
- (string-to-bytes (format nil "~{~{~a~%~}~}" (nreverse (screen-last-msg screen))))
- :string 8)
- (setf (screen-last-msg screen) msgs
- (screen-last-msg-highlights screen) hlts))
- (xlib:display-finish-output *display*)))
-
-(defun update-window-properties (window atom)
- (case atom
- (:wm_name
- (setf (window-title window) (xwin-name (window-xwin window)))
- ;; Let the mode line know about the new name.
- (update-all-mode-lines))
- (:wm_normal_hints
- (setf (window-normal-hints window) (xlib:wm-normal-hints (window-xwin window))
- (window-type window) (xwin-type (window-xwin window)))
- (dformat 4 "new hints: ~s~%" (window-normal-hints window))
- (maximize-window window))
- (:wm_hints)
- (:wm_class
- (setf (window-class window) (xwin-class (window-xwin window))
- (window-res window) (xwin-res-name (window-xwin window))))
- (:wm_window_role
- (setf (window-role window) (xwin-role (window-xwin window))))
- (:wm_transient_for
- (setf (window-type window) (xwin-type (window-xwin window)))
- (maximize-window window))
- (:_NET_WM_STATE
- ;; Client is broken and sets this property itself instead of sending a
- ;; client request to the root window. Try to make do.
- (dolist (p (xlib:get-property (window-xwin window) :_NET_WM_STATE))
- (case (xlib:atom-name *display* p)
- (:_NET_WM_STATE_FULLSCREEN
- ;; FIXME: what about when properties are REMOVED?
- (update-fullscreen window 1)))))))
-
-(define-stump-event-handler :property-notify (window atom state)
- (dformat 2 "property notify ~s ~s ~s~%" window atom state)
- (case atom
- (:rp_command_request
- ;; we will only find the screen if window is a root window, which
- ;; is the only place we listen for ratpoison commands.
- (let* ((screen (find-screen window)))
- (when (and (eq state :new-value)
- screen)
- (handle-rp-commands window))))
- (:stumpwm_command
- ;; RP commands are too weird and problematic, KISS.
- (let* ((screen (find-screen window)))
- (when (and (eq state :new-value)
- screen)
- (handle-stumpwm-commands window))))
- (t
- (let ((window (find-window window)))
- (when window
- (update-window-properties window atom))))))
-
-(define-stump-event-handler :mapping-notify (request start count)
- ;; We could be a bit more intelligent about when to update the
- ;; modifier map, but I don't think it really matters.
- (xlib:mapping-notify *display* request start count)
- (update-modifier-map)
- (sync-keys))
-
-(define-stump-event-handler :selection-request (requestor property selection target time)
- (send-selection requestor property selection target time))
-
-(define-stump-event-handler :selection-clear ()
- (setf *x-selection* nil))
-
-(defun find-message-window-screen (win)
- "Return the screen, if any, that message window WIN belongs."
- (dolist (screen *screen-list*)
- (when (xlib:window-equal (screen-message-window screen) win)
- (return screen))))
-
-(defun draw-cross (screen window x y width height)
- (xlib:draw-line window
- (screen-frame-outline-gc screen)
- x y
- width height
- t)
- (xlib:draw-line window
- (screen-frame-outline-gc screen)
- x (+ y height)
- (+ x width) y))
-
-(define-stump-event-handler :exposure (window x y width height count)
- (let (screen ml)
- (when (zerop count)
- (cond
- ((setf screen (find-screen window))
- ;; root exposed
- (show-frame-outline (screen-current-group screen) nil))
- ((setf screen (find-message-window-screen window))
- ;; message window exposed
- (if (plusp (screen-ignore-msg-expose screen))
- (decf (screen-ignore-msg-expose screen))
- (redraw-current-message screen)))
- ((setf ml (find-mode-line-window window))
- (setf screen (mode-line-screen ml))
- (redraw-mode-line ml t)))
- ;; Show the area.
- (when (and *debug-expose-events* screen)
- (draw-cross screen window x y width height)))))
-
-
-(define-stump-event-handler :reparent-notify (window parent)
- (let ((win (find-window window)))
- (when (and win
- (not (xlib:window-equal parent (window-parent win))))
- ;; reparent it back
- (unless (eq (xlib:window-map-state (window-xwin win)) :unmapped)
- (incf (window-unmap-ignores win)))
- (xlib:reparent-window (window-xwin win) (window-parent win) 0 0))))
-
-
-;;; Fullscreen functions
-
-(defun activate-fullscreen (window)
- (dformat 2 "client requests to go fullscreen~%")
- (add-wm-state (window-xwin window) :_NET_WM_STATE_FULLSCREEN)
- (setf (window-fullscreen window) t)
- (maximize-window window)
- (focus-window window))
-
-(defun deactivate-fullscreen (window)
- (setf (window-fullscreen window) nil)
- (dformat 2 "client requests to leave fullscreen~%")
- (remove-wm-state (window-xwin window) :_NET_WM_STATE_FULLSCREEN)
- (setf (xlib:drawable-border-width (window-parent window)) (default-border-width-for-type (window-type window)))
- (maximize-window window)
- (update-window-border window)
- (update-mode-lines (current-screen)))
-
-(defun update-fullscreen (window action)
- (let ((fullscreen-p (window-fullscreen window)))
- (case action
- (0 ; _NET_WM_STATE_REMOVE
- (when fullscreen-p
- (deactivate-fullscreen window)))
- (1 ; _NET_WM_STATE_ADD
- (unless fullscreen-p
- (activate-fullscreen window)))
- (2 ; _NET_WM_STATE_TOGGLE
- (if fullscreen-p
- (deactivate-fullscreen window)
- (activate-fullscreen window))))))
-
-
-(defun maybe-raise-window (window)
- (if (deny-request-p window *deny-raise-request*)
- (unless (or *suppress-deny-messages*
- ;; don't mention windows that are already visible
- (eq (frame-window (window-frame window)) window))
- (if (eq (window-group window) (current-group))
- (echo-string (window-screen window) (format nil "'~a' denied raise request" (window-name window)))
- (echo-string (window-screen window) (format nil "'~a' denied raise request in group ~a" (window-name window) (group-name (window-group window))))))
- (focus-all window)))
-
-(define-stump-event-handler :client-message (window type #|format|# data)
- (dformat 2 "client message: ~s ~s~%" type data)
- (case type
- (:_NET_CURRENT_DESKTOP ;switch desktop
- (let* ((screen (find-screen window))
- (n (elt data 0))
- (group (and screen
- (< n (length (screen-groups screen)))
- (elt (sort-groups screen) n))))
- (when group
- (switch-to-group group))))
- (:_NET_WM_DESKTOP ;move window to desktop
- (let* ((our-window (find-window window))
- (screen (when our-window
- (window-screen our-window)))
- (n (elt data 0))
- (group (and screen
- (< n (length (screen-groups screen)))
- (elt (sort-groups screen) n))))
- (when (and our-window group)
- (move-window-to-group our-window group))))
- (:_NET_ACTIVE_WINDOW
- (let ((our-window (find-window window))
- (source (elt data 0)))
- (when our-window
- (if (= source 2) ;request is from a pager
- (focus-all our-window)
- (maybe-raise-window our-window)))))
- (:_NET_CLOSE_WINDOW
- (let ((our-window (find-window window)))
- (when our-window
- (delete-window our-window))))
- (:_NET_WM_STATE
- (let ((our-window (find-window window)))
- (when our-window
- (let ((action (elt data 0))
- (p1 (elt data 1))
- (p2 (elt data 2)))
- (dolist (p (list p1 p2))
- (unless (= p 0)
- (case (xlib:atom-name *display* p)
- (:_NET_WM_STATE_FULLSCREEN
- (update-fullscreen our-window action)))))))))
- (:_NET_MOVERESIZE_WINDOW
- (let ((our-window (find-window window)))
- (when our-window
- (let ((x (elt data 1))
- (y (elt data 2)))
- (dformat 3 "!!! Data: ~S~%" data)
- (handle-window-move our-window x y :relative :root)))))
- (t
- (dformat 2 "ignored message~%"))))
-
-(define-stump-event-handler :focus-out (window mode kind)
- (dformat 5 "~@{~s ~}~%" window mode kind))
-
-;;; Mouse focus
-
-(defun focus-all (win)
- "Focus the window, frame, group and screen belonging to WIN. Raise
-the window in it's frame."
- (when (and win (window-frame win))
- (unmap-message-window (window-screen win))
- (switch-to-screen (window-screen win))
- (let ((frame (window-frame win))
- (group (window-group win)))
- (switch-to-group group)
- (frame-raise-window group frame win))))
-
-(define-stump-event-handler :enter-notify (window mode)
- (when (and window (eq mode :normal) (eq *mouse-focus-policy* :sloppy))
- (let ((win (find-window window)))
- (when (and win (find win (top-windows)))
- (focus-all win)))))
-
-(define-stump-event-handler :button-press (window code x y child time)
- ;; Pass click to client
- (xlib:allow-events *display* :replay-pointer time)
- (let (screen ml win)
- (cond
- ((and (setf screen (find-screen window)) (not child))
- (when (and (eq *mouse-focus-policy* :click)
- *root-click-focuses-frame*)
- (let* ((group (screen-current-group screen))
- (frame (find-frame group x y)))
- (when frame
- (focus-frame group frame))))
- (run-hook-with-args *root-click-hook* screen code x y))
- ((setf ml (find-mode-line-window window))
- (run-hook-with-args *mode-line-click-hook* ml code x y))
- ((setf win (find-window-by-parent window (visible-windows)))
- (when (eq *mouse-focus-policy* :click)
- (focus-all win))))))
-
-;; Handling event :KEY-PRESS
-;; (:DISPLAY #<XLIB:DISPLAY :0 (The X.Org Foundation R60700000)> :EVENT-KEY :KEY-PRESS :EVENT-CODE 2 :SEND-EVENT-P NIL :CODE 45 :SEQUENCE 1419 :TIME 98761213 :ROOT #<XLIB:WINDOW :0 96> :WINDOW #<XLIB:WINDOW :0 6291484> :EVENT-WINDOW #<XLIB:WINDOW :0 6291484> :CHILD
-;; #<XLIB:WINDOW :0 6291485> :ROOT-X 754 :ROOT-Y 223 :X 753 :Y 222 :STATE 4 :SAME-SCREEN-P T)
-;; H
-
-(defun handle-event (&rest event-slots &key display event-key &allow-other-keys)
- (declare (ignore display))
- (dformat 1 ">>> ~S~%" event-key)
- (let ((eventfn (gethash event-key *event-fn-table*)))
- (when eventfn
- (handler-case
- (progn
- ;; This is not the stumpwm top level, but if the restart
- ;; is in the top level then it seems the event being
- ;; processed isn't popped off the stack and is immediately
- ;; reprocessed after restarting to the top level. So fake
- ;; it, and put the restart here.
- (with-simple-restart (top-level "Return to stumpwm's top level")
- (apply eventfn event-slots))
- (xlib:display-finish-output *display*))
- ((or xlib:window-error xlib:drawable-error) (c)
- ;; Asynchronous errors are handled in the error
- ;; handler. Synchronous errors like trying to get the window
- ;; hints on a deleted window are caught and ignored here. We
- ;; do this inside the event handler so that the event is
- ;; handled. If we catch it higher up the event will not be
- ;; flushed from the queue and we'll get ourselves into an
- ;; infinite loop.
- (dformat 4 "ignore synchronous ~a~%" c))))
- (dformat 2 "<<< ~S~%" event-key)
- t))
-
-;;; Selection
-
-(defun export-selection ()
- (let* ((screen (current-screen))
- (selwin (screen-focus-window (current-screen)))
- (root (screen-root screen)))
- (xlib:set-selection-owner *display* :primary selwin)
- (unless (eq (xlib:selection-owner *display* :primary) selwin)
- (error "Can't set selection owner"))
- ;; also set the cut buffer for completeness
- (xlib:change-property root :cut-buffer0 *x-selection* :string 8 :transform #'xlib:char->card8
- :mode :replace)))
-
-(defun set-x-selection (text)
- "Set the X11 selection string to @var{string}."
- (setf *x-selection* text)
- (export-selection))
-
-(defun send-selection (requestor property selection target time)
- (dformat 1 "send-selection ~s ~s ~s ~s ~s~%" requestor property selection target time)
- (cond
- ;; they're requesting what targets are available
- ((eq target :targets)
- (xlib:change-property requestor property (list :targets :string) target 8 :mode :replace))
- ;; send them a string
- ((find target '(:string ))
- (xlib:change-property requestor property *x-selection* :string 8 :mode :replace :transform #'xlib:char->card8))
- ;; we don't know how to handle anything else
- (t
- (setf property nil)))
- (xlib:send-event requestor :selection-notify nil
- :display *display*
- :window requestor
- :selection selection
- :property property
- :target target
- :time time)
- (xlib:display-finish-output *display*))
-
-(defun get-x-selection (&optional timeout)
- "Return the x selection no matter what client own it."
- (labels ((wait-for-selection (&rest event-slots &key display event-key &allow-other-keys)
- (declare (ignore display))
- (when (eq event-key :selection-notify)
- (destructuring-bind (&key window property &allow-other-keys) event-slots
- (if property
- (xlib:get-property window property :type :string :result-type 'string :transform #'xlib:card8->char :delete-p t)
- "")))))
- (if *x-selection*
- *x-selection*
- (progn
- (xlib:convert-selection :primary :string (screen-input-window (current-screen)) :stumpwm-selection)
- ;; Note: this may spend longer than timeout in this loop but it will eventually return.
- (let ((time (get-internal-real-time)))
- (loop for ret = (xlib:process-event *display* :handler #'wait-for-selection :timeout timeout :discard-p nil)
- when (or ret
- (> (/ (- time (get-internal-real-time)) internal-time-units-per-second)
- timeout))
- ;; make sure we return a string
- return (or ret "")))))))
-
-;;; Top map push/popping
-
-(defvar *top-map-list* nil)
-
-(defun push-top-map (new-top)
- (push *top-map* *top-map-list*)
- (setf *top-map* new-top)
- (sync-keys))
-
-(defun pop-top-map ()
- (when *top-map-list*
- (setf *top-map* (pop *top-map-list*))
- (sync-keys)
- t))
-
-(defmacro save-frame-excursion (&body body)
- "Execute body and then restore the current frame."
- (let ((oframe (gensym "OFRAME"))
- (ogroup (gensym "OGROUP")))
- `(let ((,oframe (tile-group-current-frame (current-group)))
- (,ogroup (current-group)))
- (unwind-protect (progn ,@body)
- (focus-frame ,ogroup ,oframe)))))
-
diff --git a/events.lisp b/events.lisp
new file mode 100644
index 0000000..0dc10fb
--- /dev/null
+++ b/events.lisp
@@ -0,0 +1,604 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; Event handling.
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+;;; Event handler functions
+
+(defparameter *event-fn-table* (make-hash-table)
+ "A hash of event types to functions")
+
+(defmacro define-stump-event-handler (event keys &body body)
+ (let ((fn-name (gensym))
+ (event-slots (gensym)))
+ `(labels ((,fn-name (&rest ,event-slots &key ,@keys &allow-other-keys)
+ (declare (ignore ,event-slots))
+ ,@body))
+ (setf (gethash ,event *event-fn-table*) #',fn-name))))
+
+ ;(define-stump-event-handler :map-notify (event-window window override-redirect-p)
+ ; )
+
+(defun handle-mode-line-window (xwin x y width height)
+ (declare (ignore width))
+ (let ((ml (find-mode-line-window xwin)))
+ (when ml
+ (setf (xlib:drawable-height xwin) height)
+ (update-mode-line-position ml x y)
+ (resize-mode-line ml)
+ (sync-mode-line ml))))
+
+(defun handle-unmanaged-window (xwin x y width height border-width value-mask)
+ "Call this function for windows that stumpwm isn't
+ managing. Basically just give the window what it wants."
+ (labels ((has-x (mask) (= 1 (logand mask 1)))
+ (has-y (mask) (= 2 (logand mask 2)))
+ (has-w (mask) (= 4 (logand mask 4)))
+ (has-h (mask) (= 8 (logand mask 8)))
+ (has-bw (mask) (= 16 (logand mask 16)))
+ ;; (has-stackmode (mask) (= 64 (logand mask 64)))
+ )
+ (xlib:with-state (xwin)
+ (when (has-x value-mask)
+ (setf (xlib:drawable-x xwin) x))
+ (when (has-y value-mask)
+ (setf (xlib:drawable-y xwin) y))
+ (when (has-h value-mask)
+ (setf (xlib:drawable-height xwin) height))
+ (when (has-w value-mask)
+ (setf (xlib:drawable-width xwin) width))
+ (when (has-bw value-mask)
+ (setf (xlib:drawable-border-width xwin) border-width)))))
+
+(defun update-configuration (win)
+ ;; Send a synthetic configure-notify event so that the window
+ ;; knows where it is onscreen.
+ (xwin-send-configuration-notify (window-xwin win)
+ (xlib:drawable-x (window-parent win))
+ (xlib:drawable-y (window-parent win))
+ (window-width win) (window-height win) 0))
+
+(defun handle-managed-window (window width height stack-mode value-mask)
+ "This is a managed window so deal with it appropriately."
+ ;; Grant the stack-mode change (if it's mapped)
+ (set-window-geometry window :width width :height height)
+ (maximize-window window)
+ (when (and (window-in-current-group-p window)
+ ;; stack-mode change?
+ (= 64 (logand value-mask 64)))
+ (case stack-mode
+ (:above
+ (maybe-raise-window window))))
+ (update-configuration window))
+
+(defun handle-window-move (win x y relative-to &optional (value-mask -1))
+ (when *honor-window-moves*
+ (dformat 3 "Window requested new position ~D,~D relative to ~S~%" x y relative-to)
+ (labels ((has-x (mask) (= 1 (logand mask 1)))
+ (has-y (mask) (= 2 (logand mask 2))))
+ (when (or (eq relative-to :root) (has-x value-mask) (has-y value-mask))
+ (let* ((group (window-group win))
+ (pos (if (eq relative-to :parent)
+ (list
+ (+ (xlib:drawable-x (window-parent win)) x)
+ (+ (xlib:drawable-y (window-parent win)) y))
+ (list x y)))
+ (frame (apply #'find-frame group pos)))
+ (when frame
+ (pull-window win frame)))))))
+
+(define-stump-event-handler :configure-request (stack-mode #|parent|# window #|above-sibling|# x y width height border-width value-mask)
+ ;; Grant the configure request but then maximize the window after the granting.
+ (dformat 3 "CONFIGURE REQUEST ~@{~S ~}~%" stack-mode window x y width height border-width value-mask)
+ (let ((win (find-window window)))
+ (cond
+ (win
+ (handle-window-move win x y :parent value-mask)
+ (handle-managed-window win width height stack-mode value-mask))
+ ((handle-mode-line-window window x y width height))
+ (t (handle-unmanaged-window window x y width height border-width value-mask)))))
+
+(define-stump-event-handler :configure-notify (stack-mode #|parent|# window #|above-sibling|# x y width height border-width value-mask)
+ (dformat 4 "CONFIGURE NOTIFY ~@{~S ~}~%" stack-mode window x y width height border-width value-mask)
+ (let ((screen (find-screen window)))
+ (when screen
+ (let ((old-heads (copy-list (screen-heads screen))))
+ (setf (screen-heads screen) nil)
+ (let ((new-heads (make-screen-heads screen (screen-root screen))))
+ (setf (screen-heads screen) old-heads)
+ (cond
+ ((equalp old-heads new-heads)
+ (dformat 3 "Bogus configure-notify on root window of ~S~%" screen) t)
+ (t
+ (dformat 1 "Updating Xinerama configuration for ~S.~%" screen)
+ (if new-heads
+ (progn
+ (scale-screen screen new-heads)
+ (mapc 'sync-all-frame-windows (screen-groups screen))
+ (update-mode-lines screen))
+ (dformat 1 "Invalid configuration! ~S~%" new-heads)))))))))
+
+(define-stump-event-handler :map-request (parent send-event-p window)
+ (unless send-event-p
+ ;; This assumes parent is a root window and it should be.
+ (dformat 3 "map request: ~a ~a ~a~%" window parent (find-window window))
+ (let ((screen (find-screen parent))
+ (win (find-window window))
+ (wwin (find-withdrawn-window window)))
+ ;; only absorb it if it's not already managed (it could be iconic)
+ (cond
+ (win (dformat 1 "map request for mapped window ~a~%" win))
+ ((eq (xwin-type window) :dock)
+ (when wwin
+ (setf screen (window-screen wwin)))
+ (dformat 1 "window is dock-type. attempting to place in mode-line.")
+ (place-mode-line-window screen window)
+ ;; Some panels are broken and only set the dock type after they map and withdraw.
+ (when wwin
+ (setf (screen-withdrawn-windows screen) (delete wwin (screen-withdrawn-windows screen))))
+ t)
+ (wwin (restore-window wwin))
+ ((xlib:get-property window :_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR)
+ ;; Do nothing if this is a systray window (the system tray
+ ;; will handle it, if there is one, and, if there isn't the
+ ;; user doesn't want this popping up as a managed window
+ ;; anyway.
+ t)
+ (t
+ (let ((window (process-mapped-window screen window)))
+ ;; Give it focus
+ (if (deny-request-p window *deny-map-request*)
+ (unless *suppress-deny-messages*
+ (if (eq (window-group window) (current-group))
+ (echo-string (window-screen window) (format nil "'~a' denied map request" (window-name window)))
+ (echo-string (window-screen window) (format nil "'~a' denied map request in group ~a" (window-name window) (group-name (window-group window))))))
+ (frame-raise-window (window-group window) (window-frame window) window
+ (if (eq (window-frame window)
+ (tile-group-current-frame (window-group window)))
+ t nil)))))))))
+
+(define-stump-event-handler :unmap-notify (send-event-p event-window window #|configure-p|#)
+ ;; There are two kinds of unmap notify events: the straight up
+ ;; ones where event-window and window are the same, and
+ ;; substructure unmap events when the event-window is the parent
+ ;; of window.
+ (dformat 2 "UNMAP: ~s ~s ~a~%" send-event-p (not (xlib:window-equal event-window window)) (find-window window))
+ (unless (and (not send-event-p)
+ (not (xlib:window-equal event-window window)))
+ (let ((window (find-window window)))
+ ;; if we can't find the window then there's nothing we need to
+ ;; do.
+ (when window
+ (if (plusp (window-unmap-ignores window))
+ (progn
+ (dformat 3 "decrement ignores! ~d~%" (window-unmap-ignores window))
+ (decf (window-unmap-ignores window)))
+ (withdraw-window window))))))
+
+;;(define-stump-event-handler :create-notify (#|window parent x y width height border-width|# override-redirect-p))
+;; (unless (or override-redirect-p
+;; (internal-window-p (window-screen window) window))
+;; (process-new-window (window-screen window) window))
+;; (run-hook-with-args *new-window-hook* window)))
+
+(define-stump-event-handler :destroy-notify (send-event-p event-window window)
+ (unless (or send-event-p
+ (xlib:window-equal event-window window))
+ ;; Ignore structure destroy notifies and only
+ ;; use substructure destroy notifiers. This way
+ ;; event-window is the window's parent.
+ (let ((win (or (find-window window)
+ (find-withdrawn-window window))))
+ (if win
+ (destroy-window win)
+ (progn
+ (let ((ml (find-mode-line-window window)))
+ (when ml (destroy-mode-line-window ml))))))))
+
+(defun read-from-keymap (kmap &optional update-fn)
+ "Read a sequence of keys from the user, guided by the keymap,
+KMAP and return the binding or nil if the user hit an unbound sequence.
+
+The Caller is responsible for setting up the input focus."
+ (let* ((code-state (read-key-no-modifiers))
+ (code (car code-state))
+ (state (cdr code-state)))
+ (handle-keymap kmap code state nil nil update-fn)))
+
+(defun handle-keymap (kmap code state key-seq grab update-fn)
+ "Find the command mapped to the (code state) and return it."
+ ;; a symbol is assumed to have a hashtable as a value.
+ (dformat 1 "Awaiting key ~a~%" kmap)
+ (let ((keymap '()))
+ (when (and (symbolp kmap)
+ (boundp kmap)
+ (hash-table-p (symbol-value kmap)))
+ (setf
+ keymap kmap
+ kmap (symbol-value kmap)))
+ (check-type kmap hash-table)
+ (let* ((key (code-state->key code state))
+ (cmd (lookup-key kmap key))
+ (key-seq (cons key key-seq)))
+ (dformat 1 "key-press: ~S ~S ~S~%" key state cmd)
+ (run-hook-with-args *key-press-hook* key key-seq cmd)
+ (when update-fn
+ (funcall update-fn key-seq))
+ (if cmd
+ (cond
+ ((or (hash-table-p cmd)
+ (and (symbolp cmd)
+ (boundp cmd)
+ (hash-table-p (symbol-value cmd))))
+ (when grab
+ (grab-pointer (current-screen)))
+ (let* ((code-state (read-key-no-modifiers))
+ (code (car code-state))
+ (state (cdr code-state)))
+ (unwind-protect
+ (handle-keymap cmd code state key-seq nil update-fn)
+ (when grab (ungrab-pointer)))))
+ (t (values cmd key-seq)))
+ (if (find key (list (kbd "?")
+ (kbd "C-h"))
+ :test 'equalp)
+ (progn (display-keybinding keymap) (values t key-seq))
+ (values nil key-seq))))))
+
+(define-stump-event-handler :key-press (code state #|window|#)
+ (labels ((get-cmd (code state)
+ (with-focus (screen-key-window (current-screen))
+ (handle-keymap *top-map* code state nil t nil))))
+ (unwind-protect
+ ;; modifiers can sneak in with a race condition. so avoid that.
+ (unless (is-modifier code)
+ (multiple-value-bind (cmd key-seq) (get-cmd code state)
+ (cond
+ ((eq cmd t))
+ (cmd
+ (unmap-message-window (current-screen))
+ (interactive-command cmd) t)
+ (t (message "~{~a ~}not bound." (mapcar 'print-key (nreverse key-seq))))))))))
+
+(defun bytes-to-window (bytes)
+ "A sick hack to assemble 4 bytes into a 32 bit number. This is
+because ratpoison sends the rp_command_request window in 8 byte
+chunks."
+ (+ (first bytes)
+ (ash (second bytes) 8)
+ (ash (third bytes) 16)
+ (ash (fourth bytes) 24)))
+
+(defun handle-rp-commands (root)
+ "Handle a ratpoison style command request."
+ (labels ((one-cmd ()
+ (multiple-value-bind (win type format bytes-after) (xlib:get-property root :rp_command_request :end 4 :delete-p t)
+ (declare (ignore type format))
+ (setf win (xlib::lookup-window *display* (bytes-to-window win)))
+ (when (xlib:window-p win)
+ (let* ((data (xlib:get-property win :rp_command))
+ (interactive-p (car data))
+ (cmd (map 'string 'code-char (nbutlast (cdr data)))))
+ (declare (ignore interactive-p))
+ (interactive-command cmd)
+ (xlib:change-property win :rp_command_result (map 'list 'char-code "0TODO") :string 8)
+ (xlib:display-finish-output *display*)))
+ bytes-after)))
+ (loop while (> (one-cmd) 0))))
+
+(defun handle-stumpwm-commands (root)
+ "Handle a StumpWM style command request."
+ (let* ((win root)
+ (screen (find-screen root))
+ (data (xlib:get-property win :stumpwm_command :delete-p t))
+ (cmd (bytes-to-string data)))
+ (let ((msgs (screen-last-msg screen))
+ (hlts (screen-last-msg-highlights screen))
+ (*executing-stumpwm-command* t))
+ (setf (screen-last-msg screen) '()
+ (screen-last-msg-highlights screen) '())
+ (interactive-command cmd)
+ (xlib:change-property win :stumpwm_command_result
+ (string-to-bytes (format nil "~{~{~a~%~}~}" (nreverse (screen-last-msg screen))))
+ :string 8)
+ (setf (screen-last-msg screen) msgs
+ (screen-last-msg-highlights screen) hlts))
+ (xlib:display-finish-output *display*)))
+
+(defun update-window-properties (window atom)
+ (case atom
+ (:wm_name
+ (setf (window-title window) (xwin-name (window-xwin window)))
+ ;; Let the mode line know about the new name.
+ (update-all-mode-lines))
+ (:wm_normal_hints
+ (setf (window-normal-hints window) (xlib:wm-normal-hints (window-xwin window))
+ (window-type window) (xwin-type (window-xwin window)))
+ (dformat 4 "new hints: ~s~%" (window-normal-hints window))
+ (maximize-window window))
+ (:wm_hints)
+ (:wm_class
+ (setf (window-class window) (xwin-class (window-xwin window))
+ (window-res window) (xwin-res-name (window-xwin window))))
+ (:wm_window_role
+ (setf (window-role window) (xwin-role (window-xwin window))))
+ (:wm_transient_for
+ (setf (window-type window) (xwin-type (window-xwin window)))
+ (maximize-window window))
+ (:_NET_WM_STATE
+ ;; Client is broken and sets this property itself instead of sending a
+ ;; client request to the root window. Try to make do.
+ (dolist (p (xlib:get-property (window-xwin window) :_NET_WM_STATE))
+ (case (xlib:atom-name *display* p)
+ (:_NET_WM_STATE_FULLSCREEN
+ ;; FIXME: what about when properties are REMOVED?
+ (update-fullscreen window 1)))))))
+
+(define-stump-event-handler :property-notify (window atom state)
+ (dformat 2 "property notify ~s ~s ~s~%" window atom state)
+ (case atom
+ (:rp_command_request
+ ;; we will only find the screen if window is a root window, which
+ ;; is the only place we listen for ratpoison commands.
+ (let* ((screen (find-screen window)))
+ (when (and (eq state :new-value)
+ screen)
+ (handle-rp-commands window))))
+ (:stumpwm_command
+ ;; RP commands are too weird and problematic, KISS.
+ (let* ((screen (find-screen window)))
+ (when (and (eq state :new-value)
+ screen)
+ (handle-stumpwm-commands window))))
+ (t
+ (let ((window (find-window window)))
+ (when window
+ (update-window-properties window atom))))))
+
+(define-stump-event-handler :mapping-notify (request start count)
+ ;; We could be a bit more intelligent about when to update the
+ ;; modifier map, but I don't think it really matters.
+ (xlib:mapping-notify *display* request start count)
+ (update-modifier-map)
+ (sync-keys))
+
+(define-stump-event-handler :selection-request (requestor property selection target time)
+ (send-selection requestor property selection target time))
+
+(define-stump-event-handler :selection-clear ()
+ (setf *x-selection* nil))
+
+(defun find-message-window-screen (win)
+ "Return the screen, if any, that message window WIN belongs."
+ (dolist (screen *screen-list*)
+ (when (xlib:window-equal (screen-message-window screen) win)
+ (return screen))))
+
+(defun draw-cross (screen window x y width height)
+ (xlib:draw-line window
+ (screen-frame-outline-gc screen)
+ x y
+ width height
+ t)
+ (xlib:draw-line window
+ (screen-frame-outline-gc screen)
+ x (+ y height)
+ (+ x width) y))
+
+(define-stump-event-handler :exposure (window x y width height count)
+ (let (screen ml)
+ (when (zerop count)
+ (cond
+ ((setf screen (find-screen window))
+ ;; root exposed
+ (show-frame-outline (screen-current-group screen) nil))
+ ((setf screen (find-message-window-screen window))
+ ;; message window exposed
+ (if (plusp (screen-ignore-msg-expose screen))
+ (decf (screen-ignore-msg-expose screen))
+ (redraw-current-message screen)))
+ ((setf ml (find-mode-line-window window))
+ (setf screen (mode-line-screen ml))
+ (redraw-mode-line ml t)))
+ ;; Show the area.
+ (when (and *debug-expose-events* screen)
+ (draw-cross screen window x y width height)))))
+
+(define-stump-event-handler :reparent-notify (window parent)
+ (let ((win (find-window window)))
+ (when (and win
+ (not (xlib:window-equal parent (window-parent win))))
+ ;; reparent it back
+ (unless (eq (xlib:window-map-state (window-xwin win)) :unmapped)
+ (incf (window-unmap-ignores win)))
+ (xlib:reparent-window (window-xwin win) (window-parent win) 0 0))))
+
+;;; Fullscreen functions
+
+(defun activate-fullscreen (window)
+ (dformat 2 "client requests to go fullscreen~%")
+ (add-wm-state (window-xwin window) :_NET_WM_STATE_FULLSCREEN)
+ (setf (window-fullscreen window) t)
+ (maximize-window window)
+ (focus-window window))
+
+(defun deactivate-fullscreen (window)
+ (setf (window-fullscreen window) nil)
+ (dformat 2 "client requests to leave fullscreen~%")
+ (remove-wm-state (window-xwin window) :_NET_WM_STATE_FULLSCREEN)
+ (setf (xlib:drawable-border-width (window-parent window)) (default-border-width-for-type (window-type window)))
+ (maximize-window window)
+ (update-window-border window)
+ (update-mode-lines (current-screen)))
+
+(defun update-fullscreen (window action)
+ (let ((fullscreen-p (window-fullscreen window)))
+ (case action
+ (0 ; _NET_WM_STATE_REMOVE
+ (when fullscreen-p
+ (deactivate-fullscreen window)))
+ (1 ; _NET_WM_STATE_ADD
+ (unless fullscreen-p
+ (activate-fullscreen window)))
+ (2 ; _NET_WM_STATE_TOGGLE
+ (if fullscreen-p
+ (deactivate-fullscreen window)
+ (activate-fullscreen window))))))
+
+(defun maybe-raise-window (window)
+ (if (deny-request-p window *deny-raise-request*)
+ (unless (or *suppress-deny-messages*
+ ;; don't mention windows that are already visible
+ (eq (frame-window (window-frame window)) window))
+ (if (eq (window-group window) (current-group))
+ (echo-string (window-screen window) (format nil "'~a' denied raise request" (window-name window)))
+ (echo-string (window-screen window) (format nil "'~a' denied raise request in group ~a" (window-name window) (group-name (window-group window))))))
+ (focus-all window)))
+
+(define-stump-event-handler :client-message (window type #|format|# data)
+ (dformat 2 "client message: ~s ~s~%" type data)
+ (case type
+ (:_NET_CURRENT_DESKTOP ;switch desktop
+ (let* ((screen (find-screen window))
+ (n (elt data 0))
+ (group (and screen
+ (< n (length (screen-groups screen)))
+ (elt (sort-groups screen) n))))
+ (when group
+ (switch-to-group group))))
+ (:_NET_WM_DESKTOP ;move window to desktop
+ (let* ((our-window (find-window window))
+ (screen (when our-window
+ (window-screen our-window)))
+ (n (elt data 0))
+ (group (and screen
+ (< n (length (screen-groups screen)))
+ (elt (sort-groups screen) n))))
+ (when (and our-window group)
+ (move-window-to-group our-window group))))
+ (:_NET_ACTIVE_WINDOW
+ (let ((our-window (find-window window))
+ (source (elt data 0)))
+ (when our-window
+ (if (= source 2) ;request is from a pager
+ (focus-all our-window)
+ (maybe-raise-window our-window)))))
+ (:_NET_CLOSE_WINDOW
+ (let ((our-window (find-window window)))
+ (when our-window
+ (delete-window our-window))))
+ (:_NET_WM_STATE
+ (let ((our-window (find-window window)))
+ (when our-window
+ (let ((action (elt data 0))
+ (p1 (elt data 1))
+ (p2 (elt data 2)))
+ (dolist (p (list p1 p2))
+ (unless (= p 0)
+ (case (xlib:atom-name *display* p)
+ (:_NET_WM_STATE_FULLSCREEN
+ (update-fullscreen our-window action)))))))))
+ (:_NET_MOVERESIZE_WINDOW
+ (let ((our-window (find-window window)))
+ (when our-window
+ (let ((x (elt data 1))
+ (y (elt data 2)))
+ (dformat 3 "!!! Data: ~S~%" data)
+ (handle-window-move our-window x y :relative :root)))))
+ (t
+ (dformat 2 "ignored message~%"))))
+
+(define-stump-event-handler :focus-out (window mode kind)
+ (dformat 5 "~@{~s ~}~%" window mode kind))
+
+;;; Mouse focus
+
+(defun focus-all (win)
+ "Focus the window, frame, group and screen belonging to WIN. Raise
+the window in it's frame."
+ (when (and win (window-frame win))
+ (unmap-message-window (window-screen win))
+ (switch-to-screen (window-screen win))
+ (let ((frame (window-frame win))
+ (group (window-group win)))
+ (switch-to-group group)
+ (frame-raise-window group frame win))))
+
+(define-stump-event-handler :enter-notify (window mode)
+ (when (and window (eq mode :normal) (eq *mouse-focus-policy* :sloppy))
+ (let ((win (find-window window)))
+ (when (and win (find win (top-windows)))
+ (focus-all win)))))
+
+(define-stump-event-handler :button-press (window code x y child time)
+ ;; Pass click to client
+ (xlib:allow-events *display* :replay-pointer time)
+ (let (screen ml win)
+ (cond
+ ((and (setf screen (find-screen window)) (not child))
+ (when (and (eq *mouse-focus-policy* :click)
+ *root-click-focuses-frame*)
+ (let* ((group (screen-current-group screen))
+ (frame (find-frame group x y)))
+ (when frame
+ (focus-frame group frame))))
+ (run-hook-with-args *root-click-hook* screen code x y))
+ ((setf ml (find-mode-line-window window))
+ (run-hook-with-args *mode-line-click-hook* ml code x y))
+ ((setf win (find-window-by-parent window (visible-windows)))
+ (when (eq *mouse-focus-policy* :click)
+ (focus-all win))))))
+
+;; Handling event :KEY-PRESS
+;; (:DISPLAY #<XLIB:DISPLAY :0 (The X.Org Foundation R60700000)> :EVENT-KEY :KEY-PRESS :EVENT-CODE 2 :SEND-EVENT-P NIL :CODE 45 :SEQUENCE 1419 :TIME 98761213 :ROOT #<XLIB:WINDOW :0 96> :WINDOW #<XLIB:WINDOW :0 6291484> :EVENT-WINDOW #<XLIB:WINDOW :0 6291484> :CHILD
+;; #<XLIB:WINDOW :0 6291485> :ROOT-X 754 :ROOT-Y 223 :X 753 :Y 222 :STATE 4 :SAME-SCREEN-P T)
+;; H
+
+(defun handle-event (&rest event-slots &key display event-key &allow-other-keys)
+ (declare (ignore display))
+ (dformat 1 ">>> ~S~%" event-key)
+ (let ((eventfn (gethash event-key *event-fn-table*)))
+ (when eventfn
+ (handler-case
+ (progn
+ ;; This is not the stumpwm top level, but if the restart
+ ;; is in the top level then it seems the event being
+ ;; processed isn't popped off the stack and is immediately
+ ;; reprocessed after restarting to the top level. So fake
+ ;; it, and put the restart here.
+ (with-simple-restart (top-level "Return to stumpwm's top level")
+ (apply eventfn event-slots))
+ (xlib:display-finish-output *display*))
+ ((or xlib:window-error xlib:drawable-error) (c)
+ ;; Asynchronous errors are handled in the error
+ ;; handler. Synchronous errors like trying to get the window
+ ;; hints on a deleted window are caught and ignored here. We
+ ;; do this inside the event handler so that the event is
+ ;; handled. If we catch it higher up the event will not be
+ ;; flushed from the queue and we'll get ourselves into an
+ ;; infinite loop.
+ (dformat 4 "ignore synchronous ~a~%" c))))
+ (dformat 2 "<<< ~S~%" event-key)
+ t))
diff --git a/fdump.lisp b/fdump.lisp
index ff7e1de..316f174 100644
--- a/fdump.lisp
+++ b/fdump.lisp
@@ -1,5 +1,5 @@
;; fdump.lisp -- Layout save and restore routines.
-;; Copyright (C) 2007 Jonathan Liles, Shawn Betts
+;; Copyright (C) 2007-2008 Jonathan Liles, Shawn Betts
;;
;; This file is part of stumpwm.
;;
diff --git a/frame.lisp b/frame.lisp
new file mode 100644
index 0000000..a0a9eda
--- /dev/null
+++ b/frame.lisp
@@ -0,0 +1,682 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; Frame functions
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+(export '(save-frame-excursion))
+
+(defun populate-frames (group)
+ "Try to fill empty frames in GROUP with hidden windows"
+ (dolist (f (group-frames group))
+ (unless (frame-window f)
+ (choose-new-frame-window f group)
+ (when (frame-window f)
+ (maximize-window (frame-window f))
+ (unhide-window (frame-window f))))))
+
+(defun frame-by-number (group n)
+ (unless (eq n nil)
+ (find n (group-frames group)
+ :key 'frame-number
+ :test '=)))
+
+(defun find-frame (group x y)
+ "Return the frame of GROUP containing the pixel at X Y"
+ (dolist (f (group-frames group))
+ (let* ((fy (frame-y f))
+ (fx (frame-x f))
+ (fwx (+ fx (frame-width f)))
+ (fhy (+ fy (frame-height f))))
+ (when (and
+ (>= y fy) (<= y fhy)
+ (>= x fx) (<= x fwx)
+ (return f))))))
+
+
+(defun frame-set-x (frame v)
+ (decf (frame-width frame)
+ (- v (frame-x frame)))
+ (setf (frame-x frame) v))
+
+(defun frame-set-y (frame v)
+ (decf (frame-height frame)
+ (- v (frame-y frame)))
+ (setf (frame-y frame) v))
+
+(defun frame-set-r (frame v)
+ (setf (frame-width frame)
+ (- v (frame-x frame))))
+
+(defun frame-set-b (frame v)
+ (setf (frame-height frame)
+ (- v (frame-y frame))))
+
+(defun frame-r (frame)
+ (+ (frame-x frame) (frame-width frame)))
+
+(defun frame-b (frame)
+ (+ (frame-y frame) (frame-height frame)))
+
+(defun frame-intersect (f1 f2)
+ "Return a new frame representing (only) the intersection of F1 and F2. WIDTH and HEIGHT will be <= 0 if there is no overlap"
+ (let ((r (copy-frame f1)))
+ (when (> (frame-x f2) (frame-x f1))
+ (frame-set-x r (frame-x f2)))
+ (when (< (+ (frame-x f2) (frame-width f2))
+ (+ (frame-x f1) (frame-width f1)))
+ (frame-set-r r (frame-r f2)))
+ (when (> (frame-y f2) (frame-y f1))
+ (frame-set-y r (frame-y f2)))
+ (when (< (+ (frame-y f2) (frame-height f2))
+ (+ (frame-y f1) (frame-height f1)))
+ (frame-set-b r (frame-b f2)))
+ (values r)))
+
+(defun frames-overlap-p (f1 f2)
+ "Returns T if frames F1 and F2 overlap at all"
+ (and (and (frame-p f1) (frame-p f2))
+ (let ((frame (frame-intersect f1 f2)))
+ (values (not (and (plusp (frame-width frame))
+ (plusp (frame-height frame))))))))
+
+(defun frame-raise-window (g f w &optional (focus t))
+ "Raise the window w in frame f in group g. if FOCUS is
+T (default) then also focus the frame."
+ (let ((oldw (frame-window f)))
+ ;; nothing to do when W is nil
+ (setf (frame-window f) w)
+ (unless (and w (eq oldw w))
+ (if w
+ (raise-window w)
+ (mapc 'hide-window (frame-windows g f))))
+ (when focus
+ (focus-frame g f))
+ (when (and w (not (window-modal-p w)))
+ (raise-modals-of w))))
+
+(defun focus-frame (group f)
+ (let ((w (frame-window f))
+ (last (tile-group-current-frame group))
+ (show-indicator nil))
+ (setf (tile-group-current-frame group) f)
+ ;; record the last frame to be used in the fother command.
+ (unless (eq f last)
+ (setf (tile-group-last-frame group) last)
+ (run-hook-with-args *focus-frame-hook* f last)
+ (setf show-indicator t))
+ (if w
+ (focus-window w)
+ (no-focus group (frame-window last)))
+ (if show-indicator
+ (show-frame-indicator group)
+ (show-frame-outline group))))
+
+(defun frame-windows (group f)
+ (remove-if-not (lambda (w) (eq (window-frame w) f))
+ (group-windows group)))
+
+(defun frame-sort-windows (group f)
+ (remove-if-not (lambda (w) (eq (window-frame w) f))
+ (sort-windows group)))
+
+(defun copy-frame-tree (tree)
+ "Return a copy of the frame tree."
+ (cond ((null tree) tree)
+ ((typep tree 'frame)
+ (copy-structure tree))
+ (t
+ (mapcar #'copy-frame-tree tree))))
+
+(defun group-frames (group)
+ (tree-accum-fn (tile-group-frame-tree group) 'nconc 'list))
+
+(defun head-frames (group head)
+ (tree-accum-fn (tile-group-frame-head group head) 'nconc 'list))
+
+(defun find-free-frame-number (group)
+ (find-free-number (mapcar (lambda (f) (frame-number f))
+ (group-frames group))))
+
+(defun choose-new-frame-window (frame group)
+ "Find out what window should go in a newly created frame."
+ (let ((win (case *new-frame-action*
+ (:last-window (other-hidden-window group))
+ (t nil))))
+ (setf (frame-window frame) win)
+ (when win
+ (setf (window-frame win) frame))))
+
+(defun split-frame-h (group p)
+ "Return 2 new frames. The first one stealing P's number and window"
+ (let* ((w (truncate (/ (frame-width p) 2)))
+ (h (frame-height p))
+ (f1 (make-frame :number (frame-number p)
+ :x (frame-x p)
+ :y (frame-y p)
+ :width w
+ :height h
+ :window (frame-window p)))
+ (f2 (make-frame :number (find-free-frame-number group)
+ :x (+ (frame-x p) w)
+ :y (frame-y p)
+ ;; gobble up the modulo
+ :width (- (frame-width p) w)
+ :height h
+ :window nil)))
+ (run-hook-with-args *new-frame-hook* f2)
+ (values f1 f2)))
+
+(defun split-frame-v (group p)
+ "Return 2 new frames. The first one stealing P's number and window"
+ (let* ((w (frame-width p))
+ (h (truncate (/ (frame-height p) 2)))
+ (f1 (make-frame :number (frame-number p)
+ :x (frame-x p)
+ :y (frame-y p)
+ :width w
+ :height h
+ :window (frame-window p)))
+ (f2 (make-frame :number (find-free-frame-number group)
+ :x (frame-x p)
+ :y (+ (frame-y p) h)
+ :width w
+ ;; gobble up the modulo
+ :height (- (frame-height p) h)
+ :window nil)))
+ (run-hook-with-args *new-frame-hook* f2)
+ (values f1 f2)))
+
+(defun funcall-on-leaf (tree leaf fn)
+ "Return a new tree with LEAF replaced with the result of calling FN on LEAF."
+ (cond ((atom tree)
+ (if (eq leaf tree)
+ (funcall fn leaf)
+ tree))
+ (t (mapcar (lambda (sib)
+ (funcall-on-leaf sib leaf fn))
+ tree))))
+
+(defun funcall-on-node (tree fn match)
+ "Call fn on the node where match returns t."
+ (if (funcall match tree)
+ (funcall fn tree)
+ (cond ((atom tree) tree)
+ (t (mapcar (lambda (sib)
+ (funcall-on-node sib fn match))
+ tree)))))
+
+(defun replace-frame-in-tree (tree f &rest frames)
+ (funcall-on-leaf tree f (lambda (f)
+ (declare (ignore f))
+ frames)))
+
+(defun sibling-internal (tree leaf fn)
+ "helper for next-sibling and prev-sibling."
+ (cond ((atom tree) nil)
+ ((find leaf tree)
+ (let* ((rest (cdr (member leaf (funcall fn tree))))
+ (pick (car (if (null rest) (funcall fn tree) rest))))
+ (unless (eq pick leaf)
+ pick)))
+ (t (find-if (lambda (x)
+ (sibling-internal x leaf fn))
+ tree))))
+
+(defun next-sibling (tree leaf)
+ "Return the sibling of LEAF in TREE."
+ (sibling-internal tree leaf 'identity))
+
+(defun prev-sibling (tree leaf)
+ (sibling-internal tree leaf 'reverse))
+
+(defun closest-sibling (tree leaf)
+ "Return the sibling to the right/below of leaf or left/above if
+leaf is the most right/below of its siblings."
+ (let* ((parent (tree-parent tree leaf))
+ (lastp (= (position leaf parent) (1- (length parent)))))
+ (if lastp
+ (prev-sibling parent leaf)
+ (next-sibling parent leaf))))
+
+(defun migrate-frame-windows (group src dest)
+ "Migrate all windows in SRC frame to DEST frame."
+ (mapc (lambda (w)
+ (when (eq (window-frame w) src)
+ (setf (window-frame w) dest)))
+ (group-windows group)))
+
+(defun tree-accum-fn (tree acc fn)
+ "Run an accumulator function on fn applied to each leaf"
+ (cond ((null tree) nil)
+ ((atom tree)
+ (funcall fn tree))
+ (t (apply acc (mapcar (lambda (x) (tree-accum-fn x acc fn)) tree)))))
+
+(defun tree-iterate (tree fn)
+ "Call FN on every leaf in TREE"
+ (cond ((null tree) nil)
+ ((atom tree)
+ (funcall fn tree))
+ (t (mapc (lambda (x) (tree-iterate x fn)) tree))))
+
+(defun tree-x (tree)
+ (tree-accum-fn tree 'min 'frame-x))
+
+(defun tree-y (tree)
+ (tree-accum-fn tree 'min 'frame-y))
+
+(defun tree-width (tree)
+ (cond ((atom tree) (frame-width tree))
+ ((tree-row-split tree)
+ ;; in row splits, all children have the same width, so use the
+ ;; first one.
+ (tree-width (first tree)))
+ (t
+ ;; for column splits we add the width of each child
+ (reduce '+ tree :key 'tree-width))))
+
+(defun tree-height (tree)
+ (cond ((atom tree) (frame-height tree))
+ ((tree-column-split tree)
+ ;; in row splits, all children have the same width, so use the
+ ;; first one.
+ (tree-height (first tree)))
+ (t
+ ;; for column splits we add the width of each child
+ (reduce '+ tree :key 'tree-height))))
+
+(defun tree-parent (top node)
+ "Return the list in TOP that contains NODE."
+ (cond ((atom top) nil)
+ ((find node top) top)
+ (t (loop for i in top
+ thereis (tree-parent i node)))))
+
+(defun tree-leaf (top)
+ "Return a leaf of the tree. Use this when you need a leaf but
+you don't care which one."
+ (tree-accum-fn top
+ (lambda (&rest siblings)
+ (car siblings))
+ #'identity))
+
+(defun tree-row-split (tree)
+ "Return t if the children of tree are stacked vertically"
+ (loop for i in (cdr tree)
+ with head = (car tree)
+ always (= (tree-x head) (tree-x i))))
+
+(defun tree-column-split (tree)
+ "Return t if the children of tree are side-by-side"
+ (loop for i in (cdr tree)
+ with head = (car tree)
+ always (= (tree-y head) (tree-y i))))
+
+(defun tree-split-type (tree)
+ "return :row or :column"
+ (cond ((tree-column-split tree) :column)
+ ((tree-row-split tree) :row)
+ (t (error "tree-split-type unknown"))))
+
+(defun offset-tree (tree x y)
+ "move the screen's frames around."
+ (tree-iterate tree (lambda (frame)
+ (incf (frame-x frame) x)
+ (incf (frame-y frame) y))))
+
+(defun offset-tree-dir (tree amount dir)
+ (ecase dir
+ (:left (offset-tree tree (- amount) 0))
+ (:right (offset-tree tree amount 0))
+ (:top (offset-tree tree 0 (- amount)))
+ (:bottom (offset-tree tree 0 amount))))
+
+(defun expand-tree (tree amount dir)
+ "expand the frames in tree by AMOUNT in DIR direction. DIR can be :top :bottom :left :right"
+ (labels ((expand-frame (f amount dir)
+ (ecase dir
+ (:left (decf (frame-x f) amount)
+ (incf (frame-width f) amount))
+ (:right (incf (frame-width f) amount))
+ (:top (decf (frame-y f) amount)
+ (incf (frame-height f) amount))
+ (:bottom (incf (frame-height f) amount)))))
+ (cond ((null tree) nil)
+ ((atom tree)
+ (expand-frame tree amount dir))
+ ((or (and (find dir '(:left :right))
+ (tree-row-split tree))
+ (and (find dir '(:top :bottom))
+ (tree-column-split tree)))
+ (dolist (i tree)
+ (expand-tree i amount dir)))
+ (t
+ (let* ((children (if (find dir '(:left :top))
+ (reverse tree)
+ tree))
+ (sz-fn (if (find dir '(:left :right))
+ 'tree-width
+ 'tree-height))
+ (total (funcall sz-fn tree))
+ (amt-list (loop for i in children
+ for old-sz = (funcall sz-fn i)
+ collect (truncate (/ (* amount old-sz) total))))
+ (remainder (- amount (apply '+ amt-list)))
+ (ofs 0))
+ ;; spread the remainder out as evenly as possible
+ (assert (< remainder (length amt-list)))
+ (loop for i upfrom 0
+ while (> remainder 0)
+ do
+ (incf (nth i amt-list))
+ (decf remainder))
+ ;; resize proportionally
+ (loop for i in children
+ for amt in amt-list
+ do
+ (expand-tree i amt dir)
+ (offset-tree-dir i ofs dir)
+ (incf ofs amt)))))))
+
+(defun join-subtrees (tree leaf)
+ "expand the children of tree to occupy the space of
+LEAF. Return tree with leaf removed."
+ (let* ((others (remove leaf tree))
+ (newtree (if (= (length others) 1)
+ (car others)
+ others))
+ (split-type (tree-split-type tree))
+ (dir (if (eq split-type :column) :right :bottom))
+ (ofsdir (if (eq split-type :column) :left :top))
+ (amt (if (eq split-type :column)
+ (tree-width leaf)
+ (tree-height leaf)))
+ (after (cdr (member leaf tree))))
+ ;; align all children after the leaf with the edge of the
+ ;; frame before leaf.
+ (offset-tree-dir after amt ofsdir)
+ (expand-tree newtree amt dir)
+ newtree))
+
+(defun resize-tree (tree w h &optional x y)
+ "Scale TREE to width W and height H, ignoring aspect. If X and Y are
+ provided, reposition the TREE as well."
+ (let* ((tw (tree-width tree))
+ (th (tree-height tree))
+ (wf (/ 1 (/ tw w)))
+ (hf (/ 1 (/ th h)))
+ (xo (if x (- x (tree-x tree)) 0))
+ (yo (if y (- y (tree-y tree)) 0)))
+ (tree-iterate tree (lambda (f)
+ (setf (frame-height f) (round (* (frame-height f) hf))
+ (frame-y f) (round (* (frame-y f) hf))
+ (frame-width f) (round (* (frame-width f) wf))
+ (frame-x f) (round (* (frame-x f) wf)))
+ (incf (frame-y f) yo)
+ (incf (frame-x f) xo)))
+ (dformat 4 "resize-tree ~Dx~D -> ~Dx~D~%" tw th (tree-width tree) (tree-height tree))))
+
+(defun remove-frame (tree leaf)
+ "Return a new tree with LEAF and it's sibling merged into
+one."
+ (cond ((atom tree) tree)
+ ((find leaf tree)
+ (join-subtrees tree leaf))
+ (t (mapcar (lambda (sib)
+ (remove-frame sib leaf))
+ tree))))
+
+(defun sync-frame-windows (group frame)
+ "synchronize windows attached to FRAME."
+ (mapc (lambda (w)
+ (when (eq (window-frame w) frame)
+ (dformat 3 "maximizing ~S~%" w)
+ (maximize-window w)))
+ (group-windows group)))
+
+(defun sync-all-frame-windows (group)
+ "synchronize all frames in GROUP."
+ (let ((tree (tile-group-frame-tree group)))
+ (tree-iterate tree
+ (lambda (f)
+ (sync-frame-windows group f)))))
+
+(defun sync-head-frame-windows (group head)
+ "synchronize all frames in GROUP and HEAD."
+ (dolist (f (head-frames group head))
+ (sync-frame-windows group f)))
+
+(defun offset-frames (group x y)
+ "move the screen's frames around."
+ (let ((tree (tile-group-frame-tree group)))
+ (tree-iterate tree (lambda (frame)
+ (incf (frame-x frame) x)
+ (incf (frame-y frame) y)))))
+
+(defun resize-frame (group frame amount dim)
+ "Resize FRAME by AMOUNT in DIM dimension, DIM can be
+either :width or :height"
+ (check-type group group)
+ (check-type frame frame)
+ (check-type amount integer)
+ ;; (check-type dim (member :width :height))
+ (labels ((max-amount (parent node min dim-fn)
+ (dformat 10 "max ~@{~a~^ ~}~%" parent node min dim-fn)
+ (if parent
+ (- (funcall dim-fn parent)
+ (funcall dim-fn node)
+ (* min (1- (length parent))))
+ ;; no parent means the frame can't get any bigger.
+ 0)))
+ (let* ((tree (tile-group-frame-tree group))
+ (parent (tree-parent tree frame))
+ (gparent (tree-parent tree parent))
+ (split-type (tree-split-type parent)))
+ (dformat 10 "~s ~s parent: ~s ~s width: ~s h: ~s~%" dim amount split-type parent (tree-width parent) (tree-height parent))
+ ;; normalize amount
+ (let* ((max (ecase dim
+ (:width
+ (if (>= (frame-width frame) (frame-width (frame-head group frame)))
+ 0
+ (if (eq split-type :column)
+ (max-amount parent frame *min-frame-width* 'tree-width)
+ (max-amount gparent parent *min-frame-width* 'tree-width))))
+ (:height
+ (if (>= (frame-height frame) (frame-height (frame-head group frame)))
+ 0
+ (if (eq split-type :row)
+ (max-amount parent frame *min-frame-height* 'tree-height)
+ (max-amount gparent parent *min-frame-height* 'tree-height))))))
+ (min (ecase dim
+ ;; Frames taking up the entire HEAD in one
+ ;; dimension can't be resized in that dimension.
+ (:width
+ (if (and (eq split-type :row)
+ (or (null gparent)
+ (>= (frame-width frame) (frame-width (frame-head group frame)))))
+ 0
+ (- *min-frame-width* (frame-width frame))))
+ (:height
+ (if (and (eq split-type :column)
+ (or (null gparent)
+ (>= (frame-height frame) (frame-height (frame-head group frame)))))
+ 0
+ (- *min-frame-height* (frame-height frame)))))))
+ (setf amount (max (min amount max) min))
+ (dformat 10 "bounds ~d ~d ~d~%" amount max min))
+ ;; if FRAME is taking up the whole DIM or if AMOUNT = 0, do nothing
+ (unless (zerop amount)
+ (let* ((resize-parent (or (and (eq split-type :column)
+ (eq dim :height))
+ (and (eq split-type :row)
+ (eq dim :width))))
+ (to-resize (if resize-parent parent frame))
+ (to-resize-parent (if resize-parent gparent parent))
+ (lastp (= (position to-resize to-resize-parent) (1- (length to-resize-parent))))
+ (to-shrink (if lastp
+ (prev-sibling to-resize-parent to-resize)
+ (next-sibling to-resize-parent to-resize))))
+ (expand-tree to-resize amount (ecase dim
+ (:width (if lastp :left :right))
+ (:height (if lastp :top :bottom))))
+ (expand-tree to-shrink (- amount) (ecase dim
+ (:width (if lastp :right :left))
+ (:height (if lastp :bottom :top))))
+ (unless (and *resize-hides-windows* (eq *top-map* *resize-map*))
+ (tree-iterate to-resize
+ (lambda (leaf)
+ (sync-frame-windows group leaf)))
+ (tree-iterate to-shrink
+ (lambda (leaf)
+ (sync-frame-windows group leaf)))))))))
+
+(defun balance-frames-internal (group tree)
+ "Resize all the children of tree to be of equal width or height
+depending on the tree's split direction."
+ (let* ((split-type (tree-split-type tree))
+ (fn (if (eq split-type :column)
+ 'tree-width
+ 'tree-height))
+ (side (if (eq split-type :column)
+ :right
+ :bottom))
+ (total (funcall fn tree))
+ size rem)
+ (multiple-value-setq (size rem) (truncate total (length tree)))
+ (loop
+ for i in tree
+ for j = rem then (1- j)
+ for totalofs = 0 then (+ totalofs ofs)
+ for ofs = (+ (- size (funcall fn i)) (if (plusp j) 1 0))
+ do
+ (expand-tree i ofs side)
+ (offset-tree-dir i totalofs side)
+ (tree-iterate i (lambda (leaf)
+ (sync-frame-windows group leaf))))))
+
+(defun split-frame (group how)
+ "split the current frame into 2 frames. return T if it succeeded. NIL otherwise."
+ (check-type how (member :row :column))
+ (let* ((frame (tile-group-current-frame group))
+ (head (frame-head group frame)))
+ ;; don't create frames smaller than the minimum size
+ (when (or (and (eq how :row)
+ (>= (frame-height frame) (* *min-frame-height* 2)))
+ (and (eq how :column)
+ (>= (frame-width frame) (* *min-frame-width* 2))))
+ (multiple-value-bind (f1 f2) (funcall (if (eq how :column)
+ 'split-frame-h
+ 'split-frame-v)
+ group frame)
+ (setf (tile-group-frame-head group head)
+ (if (atom (tile-group-frame-head group head))
+ (list f1 f2)
+ (funcall-on-node (tile-group-frame-head group head)
+ (lambda (tree)
+ (if (eq (tree-split-type tree) how)
+ (list-splice-replace frame tree f1 f2)
+ (substitute (list f1 f2) frame tree)))
+ (lambda (tree)
+ (unless (atom tree)
+ (find frame tree))))))
+ (migrate-frame-windows group frame f1)
+ (choose-new-frame-window f2 group)
+ (if (eq (tile-group-current-frame group)
+ frame)
+ (setf (tile-group-current-frame group) f1))
+ (setf (tile-group-last-frame group) f2)
+ (sync-frame-windows group f1)
+ (sync-frame-windows group f2)
+ ;; we also need to show the new window in the other frame
+ (when (frame-window f2)
+ (unhide-window (frame-window f2)))
+ t))))
+
+(defun draw-frame-outline (group f tl br)
+ "Draw an outline around FRAME."
+ (let* ((screen (group-screen group))
+ (win (if (frame-window f) (window-xwin (frame-window f)) (screen-root screen)))
+ (width (screen-frame-outline-width screen))
+ (gc (screen-frame-outline-gc screen))
+ (halfwidth (/ width 2)))
+ (let ((x (frame-x f))
+ (y (frame-display-y group f))
+ (w (frame-width f))
+ (h (frame-display-height group f)))
+ (when tl
+ (xlib:draw-line win gc
+ x (+ halfwidth y) w 0 t)
+ (xlib:draw-line win gc
+ (+ halfwidth x) y 0 h t))
+ (when br
+ (xlib:draw-line win gc
+ (+ x (- w halfwidth)) y 0 h t)
+ (xlib:draw-line win gc
+ x (+ y (- h halfwidth)) w 0 t)))))
+
+(defun draw-frame-outlines (group &optional head)
+ "Draw an outline around all frames in GROUP."
+ (clear-frame-outlines group)
+ (dolist (h (if head (list head) (group-heads group)))
+ (draw-frame-outline group h nil t)
+ (tree-iterate (tile-group-frame-head group h) (lambda (f)
+ (draw-frame-outline group f t nil)))))
+
+(defun clear-frame-outlines (group)
+ "Clear the outlines drawn with DRAW-FRAME-OUTLINES."
+ (xlib:clear-area (screen-root (group-screen group))))
+
+(defun draw-frame-numbers (group)
+ "Draw the number of each frame in its corner. Return the list of
+windows used to draw the numbers in. The caller must destroy them."
+ (let ((screen (group-screen group)))
+ (mapcar (lambda (f)
+ (let ((w (xlib:create-window
+ :parent (screen-root screen)
+ :x (frame-x f) :y (frame-display-y group f) :width 1 :height 1
+ :background (screen-fg-color screen)
+ :border (screen-border-color screen)
+ :border-width 1
+ :event-mask '())))
+ (xlib:map-window w)
+ (setf (xlib:window-priority w) :above)
+ (echo-in-window w (screen-font screen)
+ (screen-fg-color screen)
+ (screen-bg-color screen)
+ (string (get-frame-number-translation f)))
+ (xlib:display-finish-output *display*)
+ (dformat 3 "mapped ~S~%" (frame-number f))
+ w))
+ (group-frames group))))
+
+(defmacro save-frame-excursion (&body body)
+ "Execute body and then restore the current frame."
+ (let ((oframe (gensym "OFRAME"))
+ (ogroup (gensym "OGROUP")))
+ `(let ((,oframe (tile-group-current-frame (current-group)))
+ (,ogroup (current-group)))
+ (unwind-protect (progn ,@body)
+ (focus-frame ,ogroup ,oframe)))))
+
diff --git a/group.lisp b/group.lisp
new file mode 100644
index 0000000..d292187
--- /dev/null
+++ b/group.lisp
@@ -0,0 +1,231 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; All group related code resides here
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+(export '(current-group))
+
+(defun current-group (&optional (screen (current-screen)))
+ "Return the current group for the current screen, unless
+otherwise specified."
+ (screen-current-group screen))
+
+(defun move-group-to-head (screen group)
+ "Move window to the head of the group's window list."
+ ;(assert (member window (screen-mapped-windows screen)))
+ (setf (screen-groups screen) (delete group (screen-groups screen)))
+ (push group (screen-groups screen)))
+
+(defun sort-groups (screen)
+ "Return a copy of the screen's group list sorted by number."
+ (sort1 (screen-groups screen) '< :key 'group-number))
+
+(defun fmt-group-status (group)
+ (let ((screen (group-screen group)))
+ (cond ((eq group (screen-current-group screen))
+ #\*)
+ ((and (typep (second (screen-groups screen)) 'group)
+ (eq group (second (screen-groups screen))))
+ #\+)
+ (t #\-))))
+
+(defun find-free-group-number (screen)
+ "Return a free group number in SCREEN."
+ (find-free-number (mapcar 'group-number (screen-groups screen)) 1))
+
+(defun find-free-hidden-group-number (screen)
+ "Return a free hidden group number for SCREEN. Hidden group numbers
+start at -1 and go down."
+ (find-free-number (mapcar 'group-number (screen-groups screen)) -1 :negative))
+
+(defun group-current-window (group)
+ (frame-window (tile-group-current-frame group)))
+
+(defun non-hidden-groups (groups)
+ "Return only those groups that are not hidden."
+ (remove-if (lambda (g)
+ (< (group-number g) 1))
+ groups))
+
+(defun netwm-group-id (group)
+ "netwm specifies that desktop/group numbers are contiguous and start
+at 0. Return a netwm compliant group id."
+ (let ((screen (group-screen group)))
+ (position group (sort-groups screen))))
+
+(defun switch-to-group (new-group)
+ (let* ((screen (group-screen new-group))
+ (old-group (screen-current-group screen)))
+ (unless (eq new-group old-group)
+ ;; restore the visible windows
+ (dolist (w (group-windows new-group))
+ (when (eq (window-state w) +normal-state+)
+ (xwin-unhide (window-xwin w) (window-parent w))))
+ (dolist (w (reverse (group-windows old-group)))
+ (when (eq (window-state w) +normal-state+)
+ (xwin-hide w)))
+ (setf (screen-current-group screen) new-group)
+ (move-group-to-head screen new-group)
+ ;; restore the focus
+ (setf (screen-focus screen) nil)
+ (focus-frame new-group (tile-group-current-frame new-group))
+ (xlib:change-property (screen-root screen) :_NET_CURRENT_DESKTOP
+ (list (netwm-group-id new-group))
+ :cardinal 32)
+ (run-hook-with-args *focus-group-hook* new-group old-group)))
+ (show-frame-indicator new-group))
+
+(defun move-window-to-group (window to-group)
+ (labels ((really-move-window (window to-group)
+ (unless (eq (window-group window) to-group)
+ (let ((old-group (window-group window))
+ (old-frame (window-frame window)))
+ (hide-window window)
+ ;; house keeping
+ (setf (group-windows (window-group window))
+ (remove window (group-windows (window-group window))))
+ (setf (window-frame window) (tile-group-current-frame to-group)
+ (window-group window) to-group
+ (window-number window) (find-free-window-number to-group))
+ ;; try to put the window in the appropriate frame for the group
+ (multiple-value-bind (placed-group frame raise) (get-window-placement (window-screen window) window)
+ (declare (ignore placed-group))
+ (when frame
+ (setf (window-frame window) frame)
+ (when raise
+ (setf (tile-group-current-frame to-group) frame
+ (frame-window frame) nil))))
+ (push window (group-windows to-group))
+ (sync-frame-windows to-group (tile-group-current-frame to-group))
+ ;; maybe pick a new window for the old frame
+ (when (eq (frame-window old-frame) window)
+ (setf (frame-window old-frame) (first (frame-windows old-group old-frame)))
+ (focus-frame old-group old-frame))
+ ;; maybe show the window in its new frame
+ (when (null (frame-window (window-frame window)))
+ (frame-raise-window (window-group window) (window-frame window) window))
+ (xlib:change-property (window-xwin window) :_NET_WM_DESKTOP
+ (list (netwm-group-id to-group))
+ :cardinal 32)))))
+ ;; When a modal window is moved, all the windows it shadows must be moved
+ ;; as well. When a shadowed window is moved, the modal shadowing it must
+ ;; be moved.
+ (cond
+ ((window-modal-p window)
+ (mapc (lambda (w)
+ (really-move-window w to-group))
+ (append (list window) (shadows-of window))))
+ ((modals-of window)
+ (mapc (lambda (w)
+ (move-window-to-group w to-group))
+ (modals-of window)))
+ (t
+ (really-move-window window to-group)))))
+
+(defun next-group (current &optional (list (screen-groups (group-screen current))))
+ ;; ditch the negative groups
+ (setf list (non-hidden-groups list))
+ (let* ((matches (member current list)))
+ (if (null (cdr matches))
+ ;; If the last one in the list is current, then
+ ;; use the first one.
+ (car list)
+ ;; Otherwise, use the next one in the list.
+ (cadr matches))))
+
+(defun merge-groups (from-group to-group)
+ "Merge all windows in FROM-GROUP into TO-GROUP."
+ (dolist (window (group-windows from-group))
+ (move-window-to-group window to-group)))
+
+(defun netwm-update-groups (screen)
+ "update all windows to reflect a change in the group list."
+ ;; FIXME: This could be optimized only to update windows when there
+ ;; is a need.
+ (loop for i from 0
+ for group in (sort-groups screen)
+ do (dolist (w (group-windows group))
+ (xlib:change-property (window-xwin w) :_NET_WM_DESKTOP
+ (list i)
+ :cardinal 32))))
+
+(defun kill-group (group to-group)
+ (when (> (length (screen-groups (group-screen group))) 1)
+ (let ((screen (group-screen group)))
+ (merge-groups group to-group)
+ (setf (screen-groups screen) (remove group (screen-groups screen)))
+ (netwm-update-groups screen))))
+
+(defun netwm-set-group-properties (screen)
+ "Set NETWM properties regarding groups of SCREEN.
+Groups are known as \"virtual desktops\" in the NETWM standard."
+ (let ((root (screen-root screen)))
+ ;; _NET_NUMBER_OF_DESKTOPS
+ (xlib:change-property root :_NET_NUMBER_OF_DESKTOPS
+ (list (length (screen-groups screen)))
+ :cardinal 32)
+ (unless *initializing*
+ ;; _NET_CURRENT_DESKTOP
+ (xlib:change-property root :_NET_CURRENT_DESKTOP
+ (list (netwm-group-id (screen-current-group screen)))
+ :cardinal 32))
+ ;; _NET_DESKTOP_NAMES
+ (xlib:change-property root :_NET_DESKTOP_NAMES
+ (let ((names (mapcan
+ (lambda (group)
+ (list (string-to-utf8 (group-name group))
+ '(0)))
+ (sort-groups screen))))
+ (apply #'concatenate 'list names))
+ :UTF8_STRING 8)))
+
+(defun add-group (screen name)
+ "Create a new group in SCREEN with the supplied name. group names
+ starting with a . are considered hidden groups. Hidden groups are
+ skipped by gprev and gnext and do not show up in the group
+ listings (unless *list-hidden-groups* is T). They also use negative
+ numbers."
+ (check-type screen screen)
+ (check-type name string)
+ (unless (or (string= name "")
+ (string= name "."))
+ (or (find-group screen name)
+ (let* ((heads (copy-heads screen))
+ (ng (make-tile-group
+ :frame-tree heads
+ :current-frame (first heads)
+ :screen screen
+ :number (if (char= (char name 0) #\.)
+ (find-free-hidden-group-number screen)
+ (find-free-group-number screen))
+ :name name)))
+ (setf (screen-groups screen) (append (screen-groups screen) (list ng)))
+ (netwm-set-group-properties screen)
+ (netwm-update-groups screen)
+ ng))))
+
+(defun find-group (screen name)
+ "Return the group with the name, NAME. Or NIL if none exists."
+ (find name (screen-groups screen) :key 'group-name :test 'string=))
diff --git a/input.lisp b/input.lisp
index 0256eb0..b5723dd 100644
--- a/input.lisp
+++ b/input.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
diff --git a/keysyms.lisp b/keysyms.lisp
index 430149e..c2fdb6f 100644
--- a/keysyms.lisp
+++ b/keysyms.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2006 Matthew Kennedy
+;; Copyright (C) 2006-2008 Matthew Kennedy
;;
;; This file is part of stumpwm.
;;
diff --git a/keytrans.lisp b/keytrans.lisp
index bd0109d..e0e8071 100644
--- a/keytrans.lisp
+++ b/keytrans.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2006 Matthew Kennedy
+;; Copyright (C) 2006-2008 Matthew Kennedy
;;
;; This file is part of stumpwm.
;;
diff --git a/kmap.lisp b/kmap.lisp
index d6fbca2..eb0984b 100644
--- a/kmap.lisp
+++ b/kmap.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
@@ -25,13 +25,18 @@
(in-package stumpwm)
-(export '(define-key
+(export '(*top-map*
+ define-key
kbd
lookup-command
lookup-key
make-sparse-keymap
undefine-key))
+(defvar *top-map* nil
+ "The top level key map. This is where you'll find the binding for the
+@dfn{prefix map}.")
+
(defstruct key
keysym shift control meta alt hyper super)
@@ -209,3 +214,26 @@ sequences that run binding."
kmap)
cmds)))
(mapcar 'reverse (search-it command keymap nil))))
+
+
+;;; The Top Map
+
+;; Do it this way so its easier to wipe the map and get a clean one.
+(when (null *top-map*)
+ (setf *top-map*
+ (let ((m (make-sparse-keymap)))
+ (define-key m (kbd "C-t") '*root-map*)
+ m)))
+
+(defvar *top-map-list* nil)
+
+(defun push-top-map (new-top)
+ (push *top-map* *top-map-list*)
+ (setf *top-map* new-top)
+ (sync-keys))
+
+(defun pop-top-map ()
+ (when *top-map-list*
+ (setf *top-map* (pop *top-map-list*))
+ (sync-keys)
+ t))
diff --git a/manual.lisp b/manual.lisp
index 45ca14a..2231831 100644
--- a/manual.lisp
+++ b/manual.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2007-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
diff --git a/message-window.lisp b/message-window.lisp
new file mode 100644
index 0000000..af40586
--- /dev/null
+++ b/message-window.lisp
@@ -0,0 +1,248 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; message printing functions
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+(export '(echo-string
+ err
+ message))
+
+(defun max-width (font l)
+ "Return the width of the longest string in L using FONT."
+ (loop for i in l
+ maximize (xlib:text-width font i :translate #'translate-id)))
+
+(defun get-gravity-coords (gravity width height minx miny maxx maxy)
+ "Return the x y coords for a window on with gravity etc"
+ (values (case gravity
+ ((:top-right :bottom-right :right) (- maxx width))
+ ((:top :bottom :center) (truncate (- maxx minx width) 2))
+ (t minx))
+ (case gravity
+ ((:bottom-left :bottom-right :bottom) (- maxy height))
+ ((:left :right :center) (truncate (- maxy miny height) 2))
+ (t miny))))
+
+(defun setup-win-gravity (screen win gravity)
+ "Position the x, y of the window according to its gravity. This
+function expects to be wrapped in a with-state for win."
+ (xlib:with-state ((screen-root screen))
+ (let ((w (xlib:drawable-width win))
+ (h (xlib:drawable-height win))
+ (screen-width (head-width (current-head)))
+ (screen-height (head-height (current-head))))
+ (let ((x (case gravity
+ ((:top-left :bottom-left) 0)
+ (:center (truncate (- screen-width w (* (xlib:drawable-border-width win) 2)) 2))
+ (t (- screen-width w (* (xlib:drawable-border-width win) 2)))))
+ (y (case gravity
+ ((:bottom-right :bottom-left) (- screen-height h (* (xlib:drawable-border-width win) 2)))
+ (:center (truncate (- screen-height h (* (xlib:drawable-border-width win) 2)) 2))
+ (t 0))))
+ (setf (xlib:drawable-y win) (max (head-y (current-head)) (+ (head-y (current-head)) y))
+ (xlib:drawable-x win) (max (head-x (current-head)) (+ (head-x (current-head)) x)))))))
+
+(defun setup-message-window (screen lines width)
+ (let ((height (* lines
+ (+ (xlib:font-ascent (screen-font screen))
+ (xlib:font-descent (screen-font screen)))))
+ (win (screen-message-window screen)))
+ ;; Now that we know the dimensions, raise and resize it.
+ (xlib:with-state (win)
+ (setf (xlib:drawable-height win) height
+ (xlib:drawable-width win) (+ width (* *message-window-padding* 2))
+ (xlib:window-priority win) :above)
+ (setup-win-gravity screen win *message-window-gravity*))
+ (xlib:map-window win)
+ ;; Clear the window
+ (xlib:clear-area win)
+ (incf (screen-ignore-msg-expose screen))
+ ;; Have to flush this or the window might get cleared
+ ;; after we've already started drawing it.
+ (xlib:display-finish-output *display*)))
+
+(defun invert-rect (screen win x y width height)
+ "invert the color in the rectangular area. Used for highlighting text."
+ (let ((gcontext (xlib:create-gcontext :drawable win
+ :foreground (screen-fg-color screen)
+ :function boole-xor)))
+ (xlib:draw-rectangle win gcontext x y width height t)
+ (setf (xlib:gcontext-foreground gcontext) (screen-bg-color screen))
+ (xlib:draw-rectangle win gcontext x y width height t)))
+
+(defun unmap-message-window (screen)
+ "Unmap the screen's message window, if it is mapped."
+ (unless (eq (xlib:window-map-state (screen-message-window screen)) :unmapped)
+ (xlib:unmap-window (screen-message-window screen))))
+
+(defun unmap-all-message-windows ()
+ (mapc #'unmap-message-window *screen-list*)
+ (when (timer-p *message-window-timer*)
+ (cancel-timer *message-window-timer*)
+ (setf *message-window-timer* nil)))
+
+(defun unmap-frame-indicator-window (screen)
+ "Unmap the screen's message window, if it is mapped."
+;; (unless (eq (xlib:window-map-state (screen-frame-window screen)) :unmapped)
+ (xlib:unmap-window (screen-frame-window screen)))
+
+(defun unmap-all-frame-indicator-windows ()
+ (mapc #'unmap-frame-indicator-window *screen-list*)
+ (when (timer-p *frame-indicator-timer*)
+ (cancel-timer *frame-indicator-timer*)
+ (setf *frame-indicator-timer* nil)))
+
+(defun reset-message-window-timer ()
+ "Set the message window timer to timeout in *timeout-wait* seconds."
+ (unless *ignore-echo-timeout*
+ (when (timer-p *message-window-timer*)
+ (cancel-timer *message-window-timer*))
+ (setf *message-window-timer* (run-with-timer *timeout-wait* nil
+ 'unmap-all-message-windows))))
+
+(defun reset-frame-indicator-timer ()
+ "Set the message window timer to timeout in *timeout-wait* seconds."
+ (when (timer-p *frame-indicator-timer*)
+ (cancel-timer *frame-indicator-timer*))
+ (setf *frame-indicator-timer* (run-with-timer *timeout-frame-indicator-wait* nil
+ 'unmap-all-frame-indicator-windows)))
+
+(defun show-frame-outline (group &optional (clear t))
+ ;; Don't draw if this isn't a current group!
+ (when (find group (mapcar 'screen-current-group *screen-list*))
+ (dformat 5 "show-frame-outline!~%")
+ ;; *resize-hides-windows* uses the frame outlines for display,
+ ;; so try not to interfere.
+ (unless (eq *top-map* *resize-map*)
+ (when clear
+ (clear-frame-outlines group))
+ (let ((frame (tile-group-current-frame group)))
+ (unless (and (= 1 (length (tile-group-frame-tree group)))
+ (atom (first (tile-group-frame-tree group))))
+ ;; draw the outline
+ (unless (frame-window frame)
+ (draw-frame-outline group frame t t)))))))
+
+(defun show-frame-indicator (group &optional force)
+ (show-frame-outline group)
+ ;; FIXME: Arg, these tests are already done in show-frame-outline
+ (when (find group (mapcar 'screen-current-group *screen-list*))
+ (when (or force
+ (and (or (> 1 (length (tile-group-frame-tree group)))
+ (not (atom (first (tile-group-frame-tree group)))))
+ (not *suppress-frame-indicator*)))
+ (let ((frame (tile-group-current-frame group))
+ (w (screen-frame-window (current-screen)))
+ (string *frame-indicator-text*)
+ (font (screen-font (current-screen))))
+ (xlib:with-state (w)
+ (setf (xlib:drawable-x w) (+ (frame-x frame)
+ (truncate (- (frame-width frame) (xlib:text-width font string)) 2))
+ (xlib:drawable-y w) (+ (frame-y frame)
+ (truncate (- (frame-height frame) (font-height font)) 2))
+ (xlib:window-priority w) :above))
+ (xlib:map-window w)
+ (echo-in-window w font (screen-fg-color (current-screen)) (screen-bg-color (current-screen)) string)
+ (reset-frame-indicator-timer)))))
+
+(defun echo-in-window (win font fg bg string)
+ (let* ((height (font-height font))
+ (gcontext (xlib:create-gcontext :drawable win
+ :font font
+ :foreground fg
+ :background bg))
+ (width (xlib:text-width font string)))
+ (xlib:with-state (win)
+ (setf (xlib:drawable-height win) height
+ (xlib:drawable-width win) width))
+ (xlib:clear-area win)
+ (xlib:display-finish-output *display*)
+ (xlib:draw-image-glyphs win gcontext 0 (xlib:font-ascent font) string)))
+
+(defun push-last-message (screen strings highlights)
+ ;; only push unique messages
+ (unless *record-last-msg-override*
+ (push strings (screen-last-msg screen))
+ (push highlights (screen-last-msg-highlights screen))
+ ;; crop for size
+ (when (>= (length (screen-last-msg screen)) *max-last-message-size*)
+ (setf (screen-last-msg screen) (butlast (screen-last-msg screen)))
+ (setf (screen-last-msg-highlights screen) (butlast (screen-last-msg-highlights screen))))))
+
+(defun redraw-current-message (screen)
+ (let ((*record-last-msg-override* t)
+ (*ignore-echo-timeout* t))
+ (dformat 5 "Redrawing message window!~%")
+ (apply 'echo-string-list screen (screen-current-msg screen) (screen-current-msg-highlights screen))))
+
+(defun echo-nth-last-message (screen n)
+ (let ((*record-last-msg-override* t))
+ (apply 'echo-string-list screen (nth n (screen-last-msg screen)) (nth n (screen-last-msg-highlights screen)))))
+
+(defun echo-string-list (screen strings &rest highlights)
+ "Draw each string in l in the screen's message window. HIGHLIGHT is
+ the nth entry to highlight."
+ (when strings
+ (unless *executing-stumpwm-command*
+ (let ((width (render-strings screen (screen-message-cc screen) *message-window-padding* 0 strings '() nil)))
+ (setup-message-window screen (length strings) width)
+ (render-strings screen (screen-message-cc screen) *message-window-padding* 0 strings highlights))
+ (setf (screen-current-msg screen)
+ strings
+ (screen-current-msg-highlights screen)
+ highlights))
+ (push-last-message screen strings highlights)
+ (xlib:display-finish-output *display*)
+ ;; Set a timer to hide the message after a number of seconds
+ (if *suppress-echo-timeout*
+ ;; any left over timers need to be canceled.
+ (when (timer-p *message-window-timer*)
+ (cancel-timer *message-window-timer*)
+ (setf *message-window-timer* nil))
+ (reset-message-window-timer))
+ (apply 'run-hook-with-args *message-hook* strings)))
+
+(defun echo-string (screen msg)
+ "Display @var{string} in the message bar on @var{screen}. You almost always want to use @command{message}."
+ (echo-string-list screen (split-string msg (string #\Newline))))
+
+(defun message (fmt &rest args)
+ "run FMT and ARGS through `format' and echo the result to the current screen."
+ (echo-string (current-screen) (apply 'format nil fmt args)))
+
+
+(defun err (fmt &rest args)
+ "run FMT and ARGS through format and echo the result to the
+current screen along with a backtrace. For careful study, the
+message does not time out."
+ (let ((*suppress-echo-timeout* t))
+ (echo-string (current-screen)
+ (concat (apply 'format nil fmt args)
+ (backtrace-string)))))
+
+(defun message-no-timeout (fmt &rest args)
+ "Like message, but the window doesn't disappear after a few seconds."
+ (let ((*suppress-echo-timeout* t))
+ (apply 'message fmt args)))
diff --git a/mode-line.lisp b/mode-line.lisp
index 03d1274..fecc1a7 100644
--- a/mode-line.lisp
+++ b/mode-line.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2006 Shawn Betts
+;; Copyright (C) 2006-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
diff --git a/package.lisp b/package.lisp
index 09b20b0..fe94fef 100644
--- a/package.lisp
+++ b/package.lisp
@@ -1,5 +1,5 @@
;; package.lisp --
-;; Copyright (C) 2003,2007 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
diff --git a/primitives.lisp b/primitives.lisp
index 8903b2a..815534c 100644
--- a/primitives.lisp
+++ b/primitives.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
@@ -78,7 +78,6 @@
*group-format*
*list-hidden-groups*
*x-selection*
- *top-map*
*last-command*
*max-last-message-size*
*record-last-msg-override*
@@ -903,11 +902,6 @@ The group's name.
"This holds stumpwm's current selection. It is generally set
when killing text in the input bar.")
-;; This is here to avoid warnings
-(defvar *top-map* nil
- "The top level key map. This is where you'll find the binding for the
-@dfn{prefix map}.")
-
(defvar *last-command* nil
"Set to the last interactive command run.")
diff --git a/screen.lisp b/screen.lisp
new file mode 100644
index 0000000..8816690
--- /dev/null
+++ b/screen.lisp
@@ -0,0 +1,616 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; Screen and head functionality
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+(export '(current-screen
+ current-window
+ screen-current-window
+ set-fg-color
+ set-bg-color
+ set-border-color
+ set-win-bg-color
+ set-focus-color
+ set-unfocus-color
+ set-msg-border-width
+ set-frame-outline-width
+ set-font))
+
+;; Screen helper functions
+
+(defun translate-id (src src-start src-end font dst dst-start)
+ "A simple replacement for xlib:translate-default. just the
+identity with a range check."
+ (let ((min (xlib:font-min-char font))
+ (max (xlib:font-max-char font)))
+ (decf src-end)
+ (if (stringp src) ; clx does this test so i guess it's needed
+ (loop for i from src-start to src-end
+ for j from dst-start
+ as c = (char-code (char src i))
+ if (<= min c max) do (setf (aref dst j) c)
+ ;; replace unknown characters with question marks
+ else do (setf (aref dst j) (char-code #\?))
+ finally (return i))
+ (loop for i from src-start to src-end
+ for j from dst-start
+ as c = (elt src i)
+ as n = (if (characterp c) (char-code c) c)
+ if (and (integerp n) (<= min n max)) do (setf (aref dst j) n)
+ ;; ditto
+ else do (setf (aref dst j) (char-code #\?))
+ finally (return i)))))
+
+(defun screen-x (screen)
+ (declare (ignore screen))
+ 0)
+
+(defun screen-y (screen)
+ (declare (ignore screen))
+ 0)
+
+(defun screen-height (screen)
+ (let ((root (screen-root screen)))
+ (xlib:drawable-height root)))
+
+(defun screen-true-height (screen)
+ "Return the height of the screen regardless of the modeline"
+ (let ((root (screen-root screen)))
+ (xlib:drawable-height root)))
+
+(defun screen-width (screen)
+ (let ((root (screen-root screen)))
+ (xlib:drawable-width root)))
+
+(defun find-screen (root)
+ "Return the screen containing the root window."
+ (find-if (lambda (s)
+ (xlib:window-equal (screen-root s) root))
+ *screen-list*))
+
+(defun screen-windows (screen)
+ (mapcan (lambda (g) (copy-list (group-windows g))) (screen-groups screen)))
+
+
+
+(defun netwm-update-client-list-stacking (screen)
+ (unless *initializing*
+ (xlib:change-property (screen-root screen)
+ :_NET_CLIENT_LIST_STACKING
+ ;; Order is bottom to top.
+ (reverse (mapcar 'window-xwin (all-windows)))
+ :window 32
+ :transform #'xlib:drawable-id
+ :mode :replace)))
+
+(defun netwm-update-client-list (screen)
+ (xlib:change-property (screen-root screen)
+ :_NET_CLIENT_LIST
+ (screen-mapped-windows screen)
+ :window 32
+ :transform #'xlib:drawable-id
+ :mode :replace)
+ (netwm-update-client-list-stacking screen))
+
+(defun screen-add-mapped-window (screen xwin)
+ (push xwin (screen-mapped-windows screen))
+ (netwm-update-client-list screen))
+
+(defun screen-remove-mapped-window (screen xwin)
+ (unregister-window xwin)
+ (setf (screen-mapped-windows screen)
+ (remove xwin (screen-mapped-windows screen)))
+ (netwm-update-client-list screen))
+
+(defun sort-screens ()
+ "Return the list of screen sorted by ID."
+ (sort1 *screen-list* '< :key 'screen-id))
+
+(defun next-screen (&optional (list (sort-screens)))
+ (let ((matches (member (current-screen) list)))
+ (if (null (cdr matches))
+ ;; If the last one in the list is current, then
+ ;; use the first one.
+ (car list)
+ ;; Otherwise, use the next one in the list.
+ (cadr matches))))
+
+(defun move-screen-to-head (screen)
+ (setf *screen-list* (remove screen *screen-list*))
+ (push screen *screen-list*))
+
+(defun switch-to-screen (screen)
+ (when (and screen
+ (not (eq screen (current-screen))))
+ (if (screen-focus screen)
+ (xlib:set-input-focus *display* (window-xwin (screen-focus screen)) :POINTER-ROOT)
+ (xlib:set-input-focus *display* (screen-focus-window screen) :POINTER-ROOT))
+ (move-screen-to-head screen)))
+
+(defun screen-set-focus (screen window)
+ (when (eq (window-group window)
+ (screen-current-group screen))
+ ;;(format t "FOCUS TO: ~a ~a~%" window (window-xwin window))
+ ;;(format t "FOCUS BEFORE: ~a~%" (multiple-value-list (xlib:input-focus *display*)))
+ ;;(format t "FOCUS RET: ~a~%" (xlib:set-input-focus *display* (window-xwin window) :POINTER-ROOT))
+ (xlib:set-input-focus *display* (window-xwin window) :POINTER-ROOT)
+ ;;(xlib:display-finish-output *display*)
+ ;;(format t "FOCUS IS: ~a~%" (multiple-value-list (xlib:input-focus *display*)))
+ (xlib:change-property (screen-root screen) :_NET_ACTIVE_WINDOW
+ (list (window-xwin window))
+ :window 32
+ :transform #'xlib:drawable-id
+ :mode :replace)
+ (setf (screen-focus screen) window)
+ (move-screen-to-head screen)))
+
+(defun screen-current-window (screen)
+ "Return the current window on the specified screen"
+ (group-current-window (screen-current-group screen)))
+
+(defun current-window ()
+ "Return the current window on the current screen"
+ (screen-current-window (current-screen)))
+
+(defun register-window (window)
+ (setf (gethash (xlib:window-id (window-xwin window)) *xwin-to-window*) window))
+
+(defun unregister-window (xwin)
+ (remhash (xlib:window-id xwin) *xwin-to-window*))
+
+(defun window-by-id (id)
+ (gethash id *xwin-to-window*))
+
+(defun find-window (xwin)
+ (window-by-id (xlib:window-id xwin)))
+
+(defun find-window-by-parent (xwin &optional (windows (all-windows)))
+ (dformat 3 "find-window-by-parent!~%")
+ (find xwin windows :key 'window-parent :test 'xlib:window-equal))
+
+(defun screen-root (screen)
+ (xlib:screen-root (screen-number screen)))
+
+(defun update-colors-for-screen (screen)
+ (let ((fg (screen-fg-color screen))
+ (bg (screen-bg-color screen)))
+ (setf (xlib:gcontext-foreground (screen-message-gc screen)) fg
+ (xlib:gcontext-background (screen-message-gc screen)) bg
+ (xlib:gcontext-foreground (screen-frame-outline-gc screen)) fg
+ (xlib:gcontext-background (screen-frame-outline-gc screen)) bg
+ (ccontext-default-fg (screen-message-cc screen)) fg
+ (ccontext-default-bg (screen-message-cc screen)) bg))
+ (dolist (i (list (screen-message-window screen)
+ (screen-input-window screen)))
+ (setf (xlib:window-border i) (screen-border-color screen)
+ (xlib:window-background i) (screen-bg-color screen)))
+ ;; update the backgrounds of all the managed windows
+ (dolist (g (screen-groups screen))
+ (dolist (w (group-windows g))
+ (unless (eq w (group-current-window g))
+ (setf (xlib:window-background (window-parent w)) (screen-win-bg-color screen))
+ (xlib:clear-area (window-parent w)))))
+ (dolist (i (screen-withdrawn-windows screen))
+ (setf (xlib:window-background (window-parent i)) (screen-win-bg-color screen))
+ (xlib:clear-area (window-parent i)))
+ (update-screen-color-context screen))
+
+(defun update-colors-all-screens ()
+ "After setting the fg, bg, or border colors. call this to sync any existing windows."
+ (mapc 'update-colors-for-screen *screen-list*))
+
+(defun update-border-for-screen (screen)
+ (setf (xlib:drawable-border-width (screen-input-window screen)) (screen-msg-border-width screen)
+ (xlib:drawable-border-width (screen-message-window screen)) (screen-msg-border-width screen)))
+
+(defun update-border-all-screens ()
+ "After setting the border width call this to sync any existing windows."
+ (mapc 'update-border-for-screen *screen-list*))
+
+(defun internal-window-p (screen win)
+ "Return t if win is a window used by stumpwm"
+ (or (xlib:window-equal (screen-message-window screen) win)
+ (xlib:window-equal (screen-input-window screen) win)
+ (xlib:window-equal (screen-focus-window screen) win)
+ (xlib:window-equal (screen-key-window screen) win)))
+
+(defun color-exists-p (color)
+ (handler-case
+ (loop for i in *screen-list*
+ always (xlib:lookup-color (xlib:screen-default-colormap (screen-number i)) color))
+ (xlib:name-error () nil)))
+
+(defun font-exists-p (font-name)
+ ;; if we can list the font then it exists
+ (plusp (length (xlib:list-font-names *display* font-name :max-fonts 1))))
+
+(defmacro set-any-color (val color)
+ `(progn (dolist (s *screen-list*)
+ (setf (,val s) (alloc-color s ,color)))
+ (update-colors-all-screens)))
+
+;; FIXME: I don't like any of this. Isn't there a way to define
+;; a setf method to call (update-colors-all-screens) when the user
+;; does eg. (setf *foreground-color* "green") instead of having
+;; these redundant set-foo functions?
+(defun set-fg-color (color)
+ "Set the foreground color for the message bar and input
+bar. @var{color} can be any color recognized by X."
+ (setf *text-color* color)
+ (set-any-color screen-fg-color color))
+
+(defun set-bg-color (color)
+ "Set the background color for the message bar and input
+bar. @var{color} can be any color recognized by X."
+ (set-any-color screen-bg-color color))
+
+(defun set-border-color (color)
+ "Set the border color for the message bar and input
+bar. @var{color} can be any color recognized by X."
+ (set-any-color screen-border-color color))
+
+(defun set-win-bg-color (color)
+ "Set the background color of the window. The background color will only
+be visible for windows with size increment hints such as @samp{emacs}
+and @samp{xterm}."
+ (set-any-color screen-win-bg-color color))
+
+(defun set-focus-color (color)
+ (set-any-color screen-focus-color color))
+
+(defun set-unfocus-color (color)
+ (set-any-color screen-unfocus-color color))
+
+(defun set-msg-border-width (width)
+ "Set the border width for the message bar and input
+bar."
+ (check-type width (integer 0))
+ (dolist (i *screen-list*)
+ (setf (screen-msg-border-width i) width))
+ (update-border-all-screens)
+ t)
+
+(defun set-frame-outline-width (width)
+ (check-type width (integer 0))
+ (dolist (i *screen-list*)
+ (setf (screen-frame-outline-width i) (if (oddp width) (1+ width) width)
+ (xlib:gcontext-line-width (screen-frame-outline-gc i)) (screen-frame-outline-width i)))
+ (update-border-all-screens)
+ t)
+
+(defun set-font (font)
+ "Set the font for the message bar and input bar."
+ (when (font-exists-p font)
+ (dolist (i *screen-list*)
+ (let ((fobj (xlib:open-font *display* (first (xlib:list-font-names *display* font :max-fonts 1)))))
+ (xlib:close-font (screen-font i))
+ (setf (screen-font i) fobj
+ (xlib:gcontext-font (screen-message-gc i)) fobj)
+ ;; update the modelines too
+ (dolist (h (screen-heads i))
+ (when (and (head-mode-line h)
+ (eq (mode-line-mode (head-mode-line h)) :stump))
+ (setf (xlib:gcontext-font (mode-line-gc (head-mode-line h))) fobj)
+ (resize-mode-line (head-mode-line h))
+ (sync-mode-line (head-mode-line h))))))
+ t))
+
+(defmacro with-current-screen (screen &body body)
+ "A macro to help us out with early set up."
+ `(let ((*screen-list* (list ,screen)))
+ ,@body))
+
+(defun current-screen ()
+ "Return the current screen."
+ (car *screen-list*))
+
+(defun netwm-set-properties (screen focus-window)
+ "Set NETWM properties on the root window of the specified screen.
+FOCUS-WINDOW is an extra window used for _NET_SUPPORTING_WM_CHECK."
+ (let* ((screen-number (screen-number screen))
+ (root (xlib:screen-root screen-number)))
+ ;; _NET_SUPPORTED
+ (xlib:change-property root :_NET_SUPPORTED
+ (mapcar (lambda (a)
+ (xlib:intern-atom *display* a))
+ (append +netwm-supported+
+ (mapcar #'car +netwm-window-types+)))
+ :atom 32)
+
+ ;; _NET_SUPPORTING_WM_CHECK
+ (xlib:change-property root :_NET_SUPPORTING_WM_CHECK
+ (list focus-window) :window 32
+ :transform #'xlib:drawable-id)
+ (xlib:change-property focus-window :_NET_SUPPORTING_WM_CHECK
+ (list focus-window) :window 32
+ :transform #'xlib:drawable-id)
+ (xlib:change-property focus-window :_NET_WM_NAME
+ "stumpwm"
+ :string 8 :transform #'xlib:char->card8)
+
+ ;; _NET_CLIENT_LIST
+ (xlib:change-property root :_NET_CLIENT_LIST
+ () :window 32
+ :transform #'xlib:drawable-id)
+
+ ;; _NET_DESKTOP_GEOMETRY
+ (xlib:change-property root :_NET_DESKTOP_GEOMETRY
+ (list (xlib:screen-width screen-number)
+ (xlib:screen-height screen-number))
+ :cardinal 32)
+
+ ;; _NET_DESKTOP_VIEWPORT
+ (xlib:change-property root :_NET_DESKTOP_VIEWPORT
+ (list 0 0) :cardinal 32)
+
+ (netwm-set-group-properties screen)))
+
+(defun init-screen (screen-number id host)
+ "Given a screen number, returns a screen structure with initialized members"
+ ;; Listen for the window manager events on the root window
+ (dformat 1 "Initializing screen: ~a ~a~%" host id)
+ (setf (xlib:window-event-mask (xlib:screen-root screen-number))
+ '(:substructure-redirect
+ :substructure-notify
+ :property-change
+ :structure-notify
+ :button-press
+ :exposure))
+ (xlib:display-finish-output *display*)
+ ;; Initialize the screen structure
+ (labels ((ac (color)
+ (xlib:alloc-color (xlib:screen-default-colormap screen-number) color)))
+ (let* ((screen (make-screen))
+ (fg (ac +default-foreground-color+))
+ (bg (ac +default-background-color+))
+ (border (ac +default-border-color+))
+ (focus (ac +default-focus-color+))
+ (unfocus (ac +default-unfocus-color+))
+ (win-bg (ac +default-window-background-color+))
+ (input-window (xlib:create-window :parent (xlib:screen-root screen-number)
+ :x 0 :y 0 :width 20 :height 20
+ :background bg
+ :border border
+ :border-width 1
+ :colormap (xlib:screen-default-colormap
+ screen-number)
+ :event-mask '(:key-press :key-release)))
+ (focus-window (xlib:create-window :parent (xlib:screen-root screen-number)
+ :x 0 :y 0 :width 1 :height 1))
+ (key-window (xlib:create-window :parent (xlib:screen-root screen-number)
+ :x 0 :y 0 :width 1 :height 1
+ :event-mask '(:key-press :key-release)))
+ (message-window (xlib:create-window :parent (xlib:screen-root screen-number)
+ :x 0 :y 0 :width 1 :height 1
+ :background bg
+ :bit-gravity :north-east
+ :border border
+ :border-width 1
+ :colormap (xlib:screen-default-colormap
+ screen-number)
+ :event-mask '(:exposure)))
+ (frame-window (xlib:create-window :parent (xlib:screen-root screen-number)
+ :x 0 :y 0 :width 20 :height 20
+ :background bg
+ :border border
+ :border-width 1
+ :colormap (xlib:screen-default-colormap
+ screen-number)
+ :event-mask '(:exposure)))
+ (font (xlib:open-font *display* +default-font-name+))
+ (group (make-tile-group
+ :screen screen
+ :number 1
+ :name *default-group-name*)))
+ ;; Create our screen structure
+ ;; The focus window is mapped at all times
+ (xlib:map-window focus-window)
+ (xlib:map-window key-window)
+ (xwin-grab-keys focus-window)
+ (setf (screen-number screen) screen-number
+ (screen-id screen) id
+ (screen-host screen) host
+ (screen-groups screen) (list group)
+ (screen-current-group screen) group
+ (screen-font screen) font
+ (screen-fg-color screen) fg
+ (screen-bg-color screen) bg
+ (screen-win-bg-color screen) win-bg
+ (screen-border-color screen) border
+ (screen-focus-color screen) focus
+ (screen-unfocus-color screen) unfocus
+ (screen-msg-border-width screen) 1
+ (screen-frame-outline-width screen) +default-frame-outline-width+
+ (screen-input-window screen) input-window
+ (screen-focus-window screen) focus-window
+ (screen-key-window screen) key-window
+ (screen-frame-window screen) frame-window
+ (screen-ignore-msg-expose screen) 0
+ (screen-message-cc screen) (make-ccontext :win message-window
+ :gc (xlib:create-gcontext
+ :drawable message-window
+ :font font
+ :foreground fg
+ :background bg))
+ (screen-frame-outline-gc screen) (xlib:create-gcontext :drawable (screen-root screen)
+ :font font
+ :foreground fg
+ :background fg
+ :line-style :double-dash
+ :line-width +default-frame-outline-width+))
+ (setf (screen-heads screen) (make-screen-heads screen (xlib:screen-root screen-number))
+ (tile-group-frame-tree group) (copy-heads screen)
+ (tile-group-current-frame group) (first (tile-group-frame-tree group)))
+ (netwm-set-properties screen focus-window)
+ (update-colors-for-screen screen)
+ (update-color-map screen)
+ screen)))
+
+
+;;; Head functions
+
+(defun head-by-number (screen n)
+ (find n (screen-heads screen) :key 'head-number))
+
+(defun parse-xinerama-head (line)
+ (ppcre:register-groups-bind (('parse-integer number width height x y))
+ ("^ +head #([0-9]+): ([0-9]+)x([0-9]+) @ ([0-9]+),([0-9]+)" line :sharedp t)
+ (handler-case
+ (make-head :number number
+ :x x :y y
+ :width width
+ :height height)
+ (parse-error ()
+ nil))))
+
+(defun make-screen-heads (screen root)
+ "or use xdpyinfo to query the xinerama extension, if it's enabled."
+ (or (and (xlib:query-extension *display* "XINERAMA")
+ (with-current-screen screen
+ ;; Ignore 'clone' heads.
+ (loop
+ for i = 0 then (1+ i)
+ for h in
+ (delete-duplicates
+ (loop for i in (split-string (run-shell-command "xdpyinfo -ext XINERAMA" t))
+ for head = (parse-xinerama-head i)
+ when head
+ collect head)
+ :test #'frames-overlap-p)
+ do (setf (head-number h) i)
+ collect h)))
+ (list (make-head :number 0
+ :x 0 :y 0
+ :width (xlib:drawable-width root)
+ :height (xlib:drawable-height root)
+ :window nil))))
+
+(defun copy-heads (screen)
+ "Return a copy of screen's heads."
+ (mapcar 'copy-frame (screen-heads screen)))
+
+
+;; Determining a frame's head based on position probably won't
+;; work with overlapping heads. Would it be better to walk
+;; up the frame tree?
+(defun frame-head (group frame)
+ (dolist (head (screen-heads (group-screen group)))
+ (when (and
+ (>= (frame-x frame) (frame-x head))
+ (>= (frame-y frame) (frame-y head))
+ (<= (+ (frame-x frame) (frame-width frame))
+ (+ (frame-x head) (frame-width head)))
+ (<= (+ (frame-y frame) (frame-height frame))
+ (+ (frame-y head) (frame-height head))))
+ (return head))))
+
+(defun group-heads (group)
+ (screen-heads (group-screen group)))
+
+(defun tile-group-frame-head (group head)
+ (elt (tile-group-frame-tree group) (position head (group-heads group))))
+
+(defun (setf tile-group-frame-head) (frame group head)
+ (setf (elt (tile-group-frame-tree group) (position head (group-heads group))) frame))
+
+(defun current-head (&optional (group (current-group)))
+ (frame-head group (tile-group-current-frame group)))
+
+(defun head-windows (group head)
+ "Returns a list of windows on HEAD of GROUP"
+ (remove-if-not
+ (lambda (w)
+ (eq head (frame-head group (window-frame w))))
+ (group-windows group)))
+
+(defun frame-is-head (group frame)
+ (< (frame-number frame) (length (group-heads group))))
+
+(defun add-head (screen head)
+ (dformat 1 "Adding head #~D~%" (head-number head))
+ (setf (screen-heads screen) (sort (push head (screen-heads screen)) #'< :key 'head-number))
+ (dolist (group (screen-groups screen))
+ (setf (tile-group-frame-tree group)
+ (sort (push (copy-frame head) (tile-group-frame-tree group))
+ #'< :key (lambda (tile)
+ (if (atom tile)
+ (frame-number tile)
+ (frame-number (car tile))))))
+ ;; Try to put something in the new frame
+ (let ((frame (tile-group-frame-head group head)))
+ (choose-new-frame-window frame group)
+ (when (frame-window frame)
+ (unhide-window (frame-window frame))))))
+
+(defun remove-head (screen head)
+ (dformat 1 "Removing head #~D~%" (head-number head))
+ (dolist (group (screen-groups screen))
+ ;; Hide its windows.
+ (let ((windows (head-windows group head)))
+ ;; Remove it from the frame tree.
+ (setf (tile-group-frame-tree group) (delete (tile-group-frame-head group head) (tile-group-frame-tree group)))
+ ;; Just set current frame to whatever.
+ (let ((frame (first (group-frames group))))
+ (setf (tile-group-current-frame group) frame
+ (tile-group-last-frame group) nil)
+ (dolist (window windows)
+ (hide-window window)
+ (setf (window-frame window) frame))))
+ ;; Try to do something with the orphaned windows
+ (populate-frames group))
+ (when (head-mode-line head)
+ (toggle-mode-line screen head))
+ ;; Remove it from SCREEN's head list.
+ (setf (screen-heads screen) (delete head (screen-heads screen))))
+
+(defun scale-head (screen oh nh)
+ "Scales head OH to match the dimensions of NH."
+ (dolist (group (screen-groups screen))
+ (resize-tree (tile-group-frame-head group oh) (head-width nh) (head-height nh) (head-x nh) (head-y nh)))
+ (setf (head-x oh) (head-x nh)
+ (head-y oh) (head-y nh)
+ (head-width oh) (head-width nh)
+ (head-height oh) (head-height nh)))
+
+(defun scale-screen (screen heads)
+ "Scale all frames of all groups of SCREEN to match the dimensions
+ of HEADS."
+ (when (< (length heads) (length (screen-heads screen)))
+ ;; Some heads were removed (or cloned), try to guess which.
+ (dolist (oh (screen-heads screen))
+ (dolist (nh heads)
+ (when (and (= (head-x nh) (head-x oh))
+ (= (head-y nh) (head-y oh)))
+ ;; Same screen position; probably the same head.
+ (setf (head-number nh) (head-number oh)))))
+ ;; Actually remove the missing heads.
+ (dolist (head (set-difference (screen-heads screen) heads :key 'head-number))
+ (remove-head screen head)))
+ (loop
+ for nh in heads
+ as oh = (find (head-number nh) (screen-heads screen) :key 'head-number)
+ do (if oh
+ (scale-head screen oh nh)
+ (add-head screen nh))))
diff --git a/selection.lisp b/selection.lisp
new file mode 100644
index 0000000..026d14a
--- /dev/null
+++ b/selection.lisp
@@ -0,0 +1,88 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; Handle the X selection.
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+(export '(get-x-selection
+ set-x-selection))
+
+(defun export-selection ()
+ (let* ((screen (current-screen))
+ (selwin (screen-focus-window (current-screen)))
+ (root (screen-root screen)))
+ (xlib:set-selection-owner *display* :primary selwin)
+ (unless (eq (xlib:selection-owner *display* :primary) selwin)
+ (error "Can't set selection owner"))
+ ;; also set the cut buffer for completeness
+ (xlib:change-property root :cut-buffer0 *x-selection* :string 8 :transform #'xlib:char->card8
+ :mode :replace)))
+
+(defun set-x-selection (text)
+ "Set the X11 selection string to @var{string}."
+ (setf *x-selection* text)
+ (export-selection))
+
+(defun send-selection (requestor property selection target time)
+ (dformat 1 "send-selection ~s ~s ~s ~s ~s~%" requestor property selection target time)
+ (cond
+ ;; they're requesting what targets are available
+ ((eq target :targets)
+ (xlib:change-property requestor property (list :targets :string) target 8 :mode :replace))
+ ;; send them a string
+ ((find target '(:string ))
+ (xlib:change-property requestor property *x-selection* :string 8 :mode :replace :transform #'xlib:char->card8))
+ ;; we don't know how to handle anything else
+ (t
+ (setf property nil)))
+ (xlib:send-event requestor :selection-notify nil
+ :display *display*
+ :window requestor
+ :selection selection
+ :property property
+ :target target
+ :time time)
+ (xlib:display-finish-output *display*))
+
+(defun get-x-selection (&optional timeout)
+ "Return the x selection no matter what client own it."
+ (labels ((wait-for-selection (&rest event-slots &key display event-key &allow-other-keys)
+ (declare (ignore display))
+ (when (eq event-key :selection-notify)
+ (destructuring-bind (&key window property &allow-other-keys) event-slots
+ (if property
+ (xlib:get-property window property :type :string :result-type 'string :transform #'xlib:card8->char :delete-p t)
+ "")))))
+ (if *x-selection*
+ *x-selection*
+ (progn
+ (xlib:convert-selection :primary :string (screen-input-window (current-screen)) :stumpwm-selection)
+ ;; Note: this may spend longer than timeout in this loop but it will eventually return.
+ (let ((time (get-internal-real-time)))
+ (loop for ret = (xlib:process-event *display* :handler #'wait-for-selection :timeout timeout :discard-p nil)
+ when (or ret
+ (> (/ (- time (get-internal-real-time)) internal-time-units-per-second)
+ timeout))
+ ;; make sure we return a string
+ return (or ret "")))))))
diff --git a/stumpwm.asd b/stumpwm.asd
index 3e863ad..d6491a7 100644
--- a/stumpwm.asd
+++ b/stumpwm.asd
@@ -31,6 +31,13 @@
(:file "kmap")
(:file "input")
(:file "core")
+ (:file "screen")
+ (:file "group")
+ (:file "frame")
+ (:file "window")
+ (:file "message-window")
+ (:file "events")
+ (:file "selection")
(:file "user")
(:file "fdump")
(:file "mode-line")
diff --git a/stumpwm.lisp b/stumpwm.lisp
index 4f4d3ee..856af10 100644
--- a/stumpwm.lisp
+++ b/stumpwm.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
diff --git a/user.lisp b/user.lisp
index 1c723af..09bcdc8 100644
--- a/user.lisp
+++ b/user.lisp
@@ -1,4 +1,4 @@
-;; Copyright (C) 2003 Shawn Betts
+;; Copyright (C) 2003-2008 Shawn Betts
;;
;; This file is part of stumpwm.
;;
@@ -41,7 +41,8 @@
run-commands
run-or-raise
run-shell-command
- set-prefix-key))
+ set-prefix-key
+ window-send-string))
(defvar *root-map* nil
"This is the keymap by default bound to @kbd{C-t}. It is known as the @dfn{prefix map}.")
diff --git a/window.lisp b/window.lisp
new file mode 100644
index 0000000..20b3335
--- /dev/null
+++ b/window.lisp
@@ -0,0 +1,1039 @@
+;; Copyright (C) 2003-2008 Shawn Betts
+;;
+;; This file is part of stumpwm.
+;;
+;; stumpwm is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; stumpwm 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. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this software; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+;; Boston, MA 02111-1307 USA
+
+;; Commentary:
+;;
+;; Window functionality.
+;;
+;; Code:
+
+(in-package #:stumpwm)
+
+(export '(def-window-attr
+ set-normal-gravity
+ set-maxsize-gravity
+ set-transient-gravity
+ set-window-geometry))
+
+;; Since StumpWM already uses the term 'group' to refer to Virtual Desktops,
+;; we'll call the grouped windows of an application a 'gang'
+
+;; maybe follow transient_for to find leader.
+(defun window-leader (window)
+ (when window
+ (or (first (window-property window :WM_CLIENT_LEADER))
+ (let ((id (window-transient-for window)))
+ (when id
+ (window-leader (window-by-id id)))))))
+
+;; A modal dialog can either shadow a single window, or all windows
+;; in its gang, depending on the value of WM_TRANSIENT_FOR
+
+;; If a window is shadowed by a modal dialog, so are any other
+;; transients belonging to that window.
+
+(defun window-transient-for (window)
+ (first (window-property window :WM_TRANSIENT_FOR)))
+
+(defun window-modal-p (window)
+ (find-wm-state (window-xwin window) :_NET_WM_STATE_MODAL))
+
+(defun window-transient-p (window)
+ (find (window-type window) '(:transient :dialog)))
+
+;; FIXME: use WM_HINTS.group_leader
+(defun window-gang (window)
+ "Return a list of other windows in WINDOW's gang."
+ (let ((leader (window-leader window))
+ (screen (window-screen window)))
+ (when leader
+ (loop for w in (screen-windows screen)
+ as l = (window-leader w)
+ if (and (not (eq w window)) l (= leader l))
+ collect w))))
+
+(defun only-modals (windows)
+ "Out of WINDOWS, return a list of those which are modal."
+ (remove-if-not 'window-modal-p (copy-list windows)))
+
+(defun x-of (window filter)
+ (let* ((root (screen-root (window-screen window)))
+ (root-id (xlib:drawable-id root))
+ (win-id (xlib:window-id (window-xwin window))))
+ (loop for w in (funcall filter (window-gang window))
+ as tr = (window-transient-for w)
+ when (or (not tr) ; modal for group
+ (eq tr root-id) ; ditto
+ (eq tr win-id)) ; modal for win
+ collect w)))
+
+
+;; The modals of a transient are the modals of the window
+;; the transient belongs to.
+(defun modals-of (window)
+ "Given WINDOW return the modal dialogs which are shadowing it, if any."
+ (loop for m in (only-modals (window-gang window))
+ when (find window (shadows-of m))
+ collect m))
+
+(defun transients-of (window)
+ "Return the transient dialogs belonging to WINDOW"
+ (x-of window 'only-transients))
+
+(defun shadows-of (window)
+ "Given modal window WINDOW return the list of windows in its shadow."
+ (let* ((root (screen-root (window-screen window)))
+ (root-id (xlib:drawable-id root))
+ (tr (window-transient-for window)))
+ (cond
+ ((or (not tr)
+ (eq tr root-id))
+ (window-gang window))
+ (t
+ (let ((w (window-by-id tr)))
+ (append (list w) (transients-of w)))))))
+
+(defun only-transients (windows)
+ "Out of WINDOWS, return a list of those which are transient."
+ (remove-if-not 'window-transient-p (copy-list windows)))
+
+(defun really-raise-window (window)
+ (frame-raise-window (window-group window) (window-frame window) window))
+
+(defun raise-modals-of (window)
+ (mapc 'really-raise-window (modals-of window)))
+
+(defun raise-modals-of-gang (window)
+ (mapc 'really-raise-window (only-modals (window-gang window))))
+
+(defun raise-transients-of-gang (window)
+ (mapc 'really-raise-window (only-transients (window-gang window))))
+
+(defun all-windows ()
+ (mapcan (lambda (s) (copy-list (screen-windows s))) *screen-list*))
+
+(defun visible-windows ()
+ "Return a list of visible windows (on all screens)"
+ (loop for s in *screen-list*
+ nconc (delete-if 'window-hidden-p (copy-list (group-windows (screen-current-group s))))))
+
+(defun top-windows ()
+ "Return a list of windows on top (on all screen)"
+ (loop for s in *screen-list*
+ nconc (mapcar 'frame-window (group-frames (screen-current-group s)))))
+
+(defun window-name (window)
+ (or (window-user-title window)
+ (case *window-name-source*
+ (:resource-name (window-res window))
+ (:class (window-class window))
+ (t (window-title window)))))
+
+(defun window-id (window)
+ (xlib:window-id (window-xwin window)))
+
+(defun window-in-current-group-p (window)
+ (eq (window-group window)
+ (screen-current-group (window-screen window))))
+
+(defun window-screen (window)
+ (group-screen (window-group window)))
+
+(defun update-window-border (window)
+ ;; give it a colored border but only if there are more than 1 frames.
+ (let* ((group (window-group window))
+ (screen (group-screen group)))
+ (let ((c (if (and (> (length (group-frames group)) 1)
+ (eq (group-current-window group) window))
+ (screen-focus-color screen)
+ (screen-unfocus-color screen))))
+ (setf (xlib:window-border (window-parent window)) c
+ ;; windows that dont fill the entire screen have a transparent background.
+ (xlib:window-background (window-parent window))
+ (if (eq (window-type window) :normal)
+ (if (eq *window-border-style* :thick)
+ c
+ (screen-unfocus-color screen))
+ :none))
+ ;; get the background updated
+ (xlib:clear-area (window-parent window)))))
+
+(defun send-client-message (window type &rest data)
+ "Send a client message to a client's window."
+ (xlib:send-event (window-xwin window)
+ :client-message nil
+ :window (window-xwin window)
+ :type type
+ :format 32
+ :data data))
+
+(defun fmt-window-status (window)
+ (let ((group (window-group window)))
+ (cond ((eq window (group-current-window group))
+ #\*)
+ ((and (typep (second (group-windows group)) 'window)
+ (eq window (second (group-windows group))))
+ #\+)
+ (t #\-))))
+
+(defun fmt-window-marked (window)
+ (if (window-marked window)
+ #\#
+ #\Space))
+
+;; (defun update-window-mark (window)
+;; "Called when we need to draw or clear the mark."
+;; ;; FIXME: This doesn't work at all. I'd like to have little squares
+;; ;; that look like clamps on the corners of the window, likes its
+;; ;; sorta grabbed. But i dunno how to properly draw them.
+;; (let* ((screen (window-screen window)))
+;; (if (window-marked window)
+;; (xlib:draw-rectangle (window-parent window) (screen-marked-gc (window-screen window))
+;; 0 0 300 200 t)
+;; (xlib:clear-area (window-parent window)))))
+
+(defun xwin-net-wm-name (win)
+ "Return the netwm wm name"
+ (let ((name (xlib:get-property win :_NET_WM_NAME)))
+ (when name
+ (utf8-to-string name))))
+
+(defun xwin-name (win)
+ (or
+ (xwin-net-wm-name win)
+ (xlib:wm-name win)))
+
+;; FIXME: should we raise the winodw or its parent?
+(defun raise-window (win)
+ "Map the window if needed and bring it to the top of the stack. Does not affect focus."
+ (when (window-hidden-p win)
+ (unhide-window win)
+ (update-configuration win))
+ (when (window-in-current-group-p win)
+ (setf (xlib:window-priority (window-parent win)) :top-if)))
+
+;; some handy wrappers
+
+(defun true-height (win)
+ (xlib:with-state (win)
+ (+ (xlib:drawable-height win) (* (xlib:drawable-border-width win) 2))))
+
+(defun true-width (win)
+ (xlib:with-state (win)
+ (+ (xlib:drawable-width win) (* (xlib:drawable-border-width win) 2))))
+
+(defun xwin-border-width (win)
+ (xlib:drawable-border-width win))
+
+(defun (setf xwin-border-width) (width win)
+ (setf (xlib:drawable-border-width win) width))
+
+(defun default-border-width-for-type (type)
+ (ecase type
+ (:dock 0)
+ (:normal *normal-border-width*)
+ (:maxsize *maxsize-border-width*)
+ ((:transient :dialog) *transient-border-width*)))
+
+(defun xwin-class (win)
+ (multiple-value-bind (res class) (xlib:get-wm-class win)
+ (declare (ignore res))
+ class))
+
+(defun xwin-res-name (win)
+ (multiple-value-bind (res class) (xlib:get-wm-class win)
+ (declare (ignore class))
+ res))
+
+(defun xwin-role (win)
+ "Return WM_WINDOW_ROLE"
+ (let ((name (xlib:get-property win :WM_WINDOW_ROLE)))
+ (dformat 10 "role: ~a~%" name)
+ (if name
+ (utf8-to-string name)
+ "")))
+
+(defmacro def-window-attr (attr)
+ "Create a new window attribute and corresponding get/set functions."
+ (let ((win (gensym))
+ (val (gensym)))
+ `(progn
+ (defun ,(intern (format nil "WINDOW-~a" attr)) (,win)
+ (gethash ,attr (window-plist ,win)))
+ (defun (setf ,(intern (format nil "WINDOW-~a" attr))) (,val ,win)
+ (setf (gethash ,attr (window-plist ,win))) ,val))))
+
+(defun sort-windows (group)
+ "Return a copy of the screen's window list sorted by number."
+ (sort1 (group-windows group) '< :key 'window-number))
+
+(defun marked-windows (group)
+ "Return the marked windows in the specified group."
+ (loop for i in (sort-windows group)
+ when (window-marked i)
+ collect i))
+
+(defun clear-window-marks (group &optional (windows (group-windows group)))
+ (dolist (w windows)
+ (setf (window-marked w) nil)))
+
+(defun (setf xwin-state) (state xwin)
+ "Set the state (iconic, normal, withdrawn) of a window."
+ (xlib:change-property xwin
+ :WM_STATE
+ (list state)
+ :WM_STATE
+ 32))
+
+(defun xwin-state (xwin)
+ "Get the state (iconic, normal, withdraw of a window."
+ (first (xlib:get-property xwin :WM_STATE)))
+
+(defun window-hidden-p (window)
+ (eql (window-state window) +iconic-state+))
+
+(defun add-wm-state (xwin state)
+ (xlib:change-property xwin :_NET_WM_STATE
+ (list (xlib:find-atom *display* state))
+ :atom 32
+ :mode :append))
+
+(defun remove-wm-state (xwin state)
+ (xlib:change-property xwin :_NET_WM_STATE
+ (delete (xlib:find-atom *display* state) (xlib:get-property xwin :_NET_WM_STATE))
+ :atom 32))
+
+(defun window-property (window prop)
+ (xlib:get-property (window-xwin window) prop))
+
+(defun find-wm-state (xwin state)
+ (find (xlib:find-atom *display* state) (xlib:get-property xwin :_NET_WM_STATE) :test #'=))
+
+(defun xwin-unhide (xwin parent)
+ (xlib:map-subwindows parent)
+ (xlib:map-window parent)
+ (setf (xwin-state xwin) +normal-state+))
+
+(defun unhide-window (window)
+ (when (window-in-current-group-p window)
+ (xwin-unhide (window-xwin window) (window-parent window)))
+ (setf (window-state window) +normal-state+)
+ ;; Mark window as unhiden
+ (remove-wm-state (window-xwin window) :_NET_WM_STATE_HIDDEN))
+
+;; Despite the naming convention, this function takes a window struct,
+;; not an xlib:window.
+(defun xwin-hide (window)
+ (declare (type window window))
+ (unless (eq (xlib:window-map-state (window-xwin window)) :unmapped)
+ (setf (xwin-state (window-xwin window)) +iconic-state+)
+ (incf (window-unmap-ignores window))
+ (xlib:unmap-window (window-parent window))
+ (xlib:unmap-subwindows (window-parent window))))
+
+(defun hide-window (window)
+ (dformat 2 "hide window: ~s~%" window)
+ (unless (eql (window-state window) +iconic-state+)
+ (setf (window-state window) +iconic-state+)
+ ;; Mark window as hidden
+ (add-wm-state (window-xwin window) :_NET_WM_STATE_HIDDEN)
+ (when (window-in-current-group-p window)
+ (xwin-hide window)
+ (when (eq window (current-window))
+ ;; If this window had the focus, try to avoid losing it.
+ (let ((group (window-group window))
+ (frame (window-frame window)))
+ (setf (frame-window frame)
+ (first (remove-if 'window-hidden-p (frame-windows group frame))))
+ (focus-frame group (tile-group-current-frame group)))))))
+
+(defun xwin-type (win)
+ "Return one of :desktop, :dock, :toolbar, :utility, :splash,
+:dialog, :transient, :maxsize and :normal. Right now
+only :dialog, :normal, :maxsize and :transient are
+actually returned; see +NETWM-WINDOW-TYPES+."
+ (or (and (let ((hints (xlib:wm-normal-hints win)))
+ (and hints (or (xlib:wm-size-hints-max-width hints)
+ (xlib:wm-size-hints-max-height hints)
+ (xlib:wm-size-hints-min-aspect hints)
+ (xlib:wm-size-hints-max-aspect hints))))
+ :maxsize)
+ (let ((net-wm-window-type (xlib:get-property win :_NET_WM_WINDOW_TYPE)))
+ (when net-wm-window-type
+ (dolist (type-atom net-wm-window-type)
+ (when (assoc (xlib:atom-name *display* type-atom) +netwm-window-types+)
+ (return (cdr (assoc (xlib:atom-name *display* type-atom) +netwm-window-types+)))))))
+ (and (xlib:get-property win :WM_TRANSIENT_FOR)
+ :transient)
+ :normal))
+
+(defun xwin-strut (screen win)
+ "Return the area that the window wants to reserve along the edges of the screen.
+Values are left, right, top, bottom, left_start_y, left_end_y,
+right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x
+and bottom_end_x."
+ (let ((net-wm-strut-partial (xlib:get-property win :_NET_WM_STRUT_PARTIAL)))
+ (if (= (length net-wm-strut-partial) 12)
+ (apply 'values net-wm-strut-partial)
+ (let ((net-wm-strut (xlib:get-property win :_NET_WM_STRUT)))
+ (if (= (length net-wm-strut) 4)
+ (apply 'values (concatenate 'list net-wm-strut
+ (list 0 (screen-height screen)
+ 0 (screen-height screen)
+ 0 (screen-width screen)
+ 0 (screen-width screen))))
+ (values 0 0 0 0 0 0 0 0 0 0 0 0))))))
+
+;; Stolen from Eclipse
+(defun xwin-send-configuration-notify (xwin x y w h bw)
+ "Send a synthetic configure notify event to the given window (ICCCM 4.1.5)"
+ (xlib:send-event xwin :configure-notify nil
+ :event-window xwin
+ :window xwin
+ :x x :y y
+ :width w
+ :height h
+ :border-width bw
+ :propagate-p nil))
+
+(defun update-window-gravity ()
+ (dolist (s *screen-list*)
+ (dolist (g (screen-groups s))
+ (mapc 'maximize-window (group-windows g)))))
+
+(defun set-normal-gravity (gravity)
+ "Set the default gravity for normal windows. Possible values are
+@code{:center} @code{:top} @code{:left} @code{:right} @code{:bottom}
+@code{:top-left} @code{:top-right} @code{:bottom-left} and
+@code{:bottom-right}."
+ (setf *normal-gravity* gravity)
+ (update-window-gravity))
+
+(defun set-maxsize-gravity (gravity)
+ "Set the default gravity for maxsize windows."
+ (setf *maxsize-gravity* gravity)
+ (update-window-gravity))
+
+(defun set-transient-gravity (gravity)
+ "Set the default gravity for transient/pop-up windows."
+ (setf *transient-gravity* gravity)
+ (update-window-gravity))
+
+(defun gravity-for-window (win)
+ (or (window-gravity win)
+ (ecase (window-type win)
+ (:dock *normal-gravity*)
+ (:normal *normal-gravity*)
+ (:maxsize *maxsize-gravity*)
+ ((:transient :dialog) *transient-gravity*))))
+
+(defun geometry-hints (win)
+ "Return hints for max width and height and increment hints. These
+hints have been modified to always be defined and never be greater
+than the root window's width and height."
+ (let* ((f (window-frame win))
+ (x (frame-x f))
+ (y (frame-display-y (window-group win) f))
+ (border (xlib:drawable-border-width (window-parent win)))
+ (fwidth (- (frame-width f) (* 2 border)))
+ (fheight (- (frame-display-height (window-group win) f)
+ (* 2 border)))
+ (width fwidth)
+ (height fheight)
+ (hints (window-normal-hints win))
+ (hints-min-width (and hints (xlib:wm-size-hints-min-width hints)))
+ (hints-min-height (and hints (xlib:wm-size-hints-min-height hints)))
+ (hints-max-width (and hints (xlib:wm-size-hints-max-width hints)))
+ (hints-max-height (and hints (xlib:wm-size-hints-max-height hints)))
+ (hints-width (and hints (xlib:wm-size-hints-base-width hints)))
+ (hints-height (and hints (xlib:wm-size-hints-base-height hints)))
+ (hints-inc-x (and hints (xlib:wm-size-hints-width-inc hints)))
+ (hints-inc-y (and hints (xlib:wm-size-hints-height-inc hints)))
+ (hints-min-aspect (and hints (xlib:wm-size-hints-min-aspect hints)))
+ (hints-max-aspect (and hints (xlib:wm-size-hints-max-aspect hints)))
+ center)
+ ;; (dformat 4 "hints: ~s~%" hints)
+ ;; determine what the width and height should be
+ (cond
+ ;; handle specially fullscreen windows.
+ ((window-fullscreen win)
+ (let ((head (frame-head (window-group win) f)))
+ (setf x (frame-x head)
+ y (frame-y head)
+ width (frame-width head)
+ height (frame-height head)
+ (xlib:window-priority (window-parent win)) :above
+ (xlib:drawable-border-width (window-parent win)) 0))
+ (return-from geometry-hints (values x y 0 0 width height t)))
+ ;; Adjust the defaults if the window is a transient_for window.
+ ((find (window-type win) '(:transient :dialog))
+ (setf center t
+ width (min (max (or hints-width 0)
+ (or hints-min-width 0)
+ (window-width win))
+ width)
+ height (min (max (or hints-height 0)
+ (or hints-min-height 0)
+ (window-height win))
+ height)))
+ ;; aspect hints are handled similar to max size hints
+ ((and hints-min-aspect hints-max-aspect)
+ (let ((ratio (/ width height)))
+ (cond ((< ratio hints-min-aspect)
+ (setf height (ceiling width hints-min-aspect)))
+ ((> ratio hints-max-aspect)
+ (setf width (ceiling (* height hints-max-aspect)))))
+ (setf center t)))
+ ;; Update our defaults if the window has the maxsize hints
+ ((or hints-max-width hints-max-height)
+ (when (and hints-max-width
+ (< hints-max-width width))
+ (setf width hints-max-width))
+ (when (and hints-max-height
+ (< hints-max-height height))
+ (setf height hints-max-height))
+ (setf center t))
+ (t
+ ;; if they have inc hints then start with the size and adjust
+ ;; based on those increments until the window fits in the frame
+ (when hints-inc-x
+ (let ((w (or hints-width (window-width win))))
+ (setf width (+ w (* hints-inc-x
+ (+ (floor (- fwidth w) hints-inc-x)))))))
+ (when hints-inc-y
+ (let ((h (or hints-height (window-height win))))
+ (setf height (+ h (* hints-inc-y
+ (+ (floor (- fheight h -1) hints-inc-y)))))))))
+ ;; adjust for gravity
+ (multiple-value-bind (wx wy) (get-gravity-coords (gravity-for-window win)
+ width height
+ 0 0
+ fwidth fheight)
+ (when (or center
+ (find *window-border-style* '(:tight :none)))
+ (setf x (+ wx (frame-x f))
+ y (+ wy (frame-display-y (window-group win) f))
+ wx 0 wy 0))
+ ;; Now return our findings
+ (values x y wx wy width height center))))
+
+(defun set-window-geometry (win &key x y width height border-width)
+ (macrolet ((update (xfn wfn v)
+ `(when ,v ;; (/= (,wfn win) ,v))
+ (setf (,xfn (window-xwin win)) ,v)
+ ,(when wfn `(setf (,wfn win) ,v)))))
+ (xlib:with-state ((window-xwin win))
+ (update xlib:drawable-x nil x)
+ (update xlib:drawable-y nil y)
+ (update xlib:drawable-width window-width width)
+ (update xlib:drawable-height window-height height)
+ (update xlib:drawable-border-width nil border-width)
+ )))
+
+(defun maximize-window (win)
+ "Maximize the window."
+ (multiple-value-bind (x y wx wy width height stick)
+ (geometry-hints win)
+ (dformat 4 "maximize window ~a x: ~d y: ~d width: ~d height: ~d stick: ~s~%" win x y width height stick)
+ ;; Move the parent window
+ (xlib:with-state ((window-parent win))
+ (setf (xlib:drawable-x (window-parent win)) x
+ (xlib:drawable-y (window-parent win)) y))
+ ;; This is the only place a window's geometry should change
+ (set-window-geometry win :x wx :y wy :width width :height height :border-width 0)
+ (xlib:with-state ((window-parent win))
+ ;; FIXME: updating the border doesn't need to be run everytime
+ ;; the window is maximized, but only when the border style or
+ ;; window type changes. The overhead is probably minimal,
+ ;; though.
+ (setf (xlib:drawable-border-width (window-parent win))
+ (case *window-border-style*
+ (:none 0)
+ (t (default-border-width-for-type (window-type win)))))
+ ;; the parent window should stick to the size of the window
+ ;; unless it isn't being maximized to fill the frame.
+ (if (or stick
+ (find *window-border-style* '(:tight :none)))
+ (setf (xlib:drawable-width (window-parent win)) (window-width win)
+ (xlib:drawable-height (window-parent win)) (window-height win))
+ (let ((frame (window-frame win)))
+ (setf (xlib:drawable-width (window-parent win)) (- (frame-width frame)
+ (* 2 (xlib:drawable-border-width (window-parent win))))
+ (xlib:drawable-height (window-parent win)) (- (frame-display-height (window-group win) frame)
+ (* 2 (xlib:drawable-border-width (window-parent win))))))))))
+
+(defun find-free-window-number (group)
+ "Return a free window number for GROUP."
+ (find-free-number (mapcar 'window-number (group-windows group))))
+
+(defun reparent-window (window)
+ ;; apparently we need to grab the server so the client doesn't get
+ ;; the mapnotify event before the reparent event. that's what fvwm
+ ;; says.
+ (xlib:with-server-grabbed (*display*)
+ (let* ((screen (window-screen window))
+ (master-window (xlib:create-window
+ :parent (screen-root screen)
+ :x (xlib:drawable-x (window-xwin window)) :y (xlib:drawable-y (window-xwin window))
+ :width (window-width window)
+ :height (window-height window)
+ ;; normal windows get a black background
+ :background (if (eq (window-type window) :normal)
+ (screen-bg-color screen)
+ :none)
+ :border (screen-unfocus-color screen)
+ :border-width (default-border-width-for-type (window-type window))
+ :event-mask *window-parent-events*)))
+ (unless (eq (xlib:window-map-state (window-xwin window)) :unmapped)
+ (incf (window-unmap-ignores window)))
+ (xlib:reparent-window (window-xwin window) master-window 0 0)
+ (xwin-grab-buttons master-window)
+ ;; ;; we need to update these values since they get set to 0,0 on reparent
+ ;; (setf (window-x window) 0
+ ;; (window-y window) 0)
+ (xlib:add-to-save-set (window-xwin window))
+ (setf (window-parent window) master-window))))
+
+(defun process-existing-windows (screen)
+ "Windows present when stumpwm starts up must be absorbed by stumpwm."
+ (let ((children (xlib:query-tree (screen-root screen)))
+ (*processing-existing-windows* t)
+ (stacking (xlib:get-property (screen-root screen) :_NET_CLIENT_LIST_STACKING :type :window)))
+ (when stacking
+ (dformat 3 "Using window stacking: ~{~X ~}~%" stacking)
+ ;; sort by _NET_CLIENT_LIST_STACKING
+ (setf children (stable-sort children #'< :key
+ (lambda (xwin)
+ (or (position (xlib:drawable-id xwin) stacking :test #'=) 0)))))
+ (dolist (win children)
+ (let ((map-state (xlib:window-map-state win))
+ (wm-state (xwin-state win)))
+ ;; Don't process override-redirect windows.
+ (unless (or (eq (xlib:window-override-redirect win) :on)
+ (internal-window-p screen win))
+ (if (eq (xwin-type win) :dock)
+ (progn
+ (dformat 1 "Window ~S is dock-type. Placing in mode-line.~%" win)
+ (place-mode-line-window screen win))
+ (if (or (eql map-state :viewable)
+ (eql wm-state +iconic-state+))
+ (progn
+ (dformat 1 "Processing ~S ~S~%" (xwin-name win) win)
+ (process-mapped-window screen win))))))))
+ (dolist (w (screen-windows screen))
+ (setf (window-state w) +normal-state+)
+ (xwin-hide w)))
+
+(defun xwin-grab-keys (win)
+ (labels ((grabit (w key)
+ (let ((code (xlib:keysym->keycodes *display* (key-keysym key))))
+ ;; some keysyms aren't mapped to keycodes so just ignore them.
+ (when code
+ (xlib:grab-key w code
+ :modifiers (x11-mods key) :owner-p t
+ :sync-pointer-p nil :sync-keyboard-p nil)
+ ;; Ignore numlock by also grabbing the keycombo with
+ ;; numlock on.
+ (when (modifiers-numlock *modifiers*)
+ (xlib:grab-key w code
+ :modifiers (x11-mods key t) :owner-p t
+ :sync-pointer-p nil :sync-keyboard-p nil))))))
+ (maphash (lambda (k v)
+ (declare (ignore v))
+ (grabit win k))
+ *top-map*)))
+
+(defun grab-keys-on-window (win)
+ (xwin-grab-keys (window-xwin win)))
+
+(defun xwin-ungrab-keys (win)
+ (xlib:ungrab-key win :any :modifiers :any))
+
+(defun ungrab-keys-on-window (win)
+ (xwin-ungrab-keys (window-xwin win)))
+
+(defun xwin-grab-buttons (win)
+ ;; FIXME: Why doesn't grabbing button :any work? We have to
+ ;; grab them one by one instead.
+ (xwin-ungrab-buttons win)
+ (loop for i from 1 to 7
+ do (xlib:grab-button win i '(:button-press)
+ :modifiers :any
+ :owner-p nil
+ :sync-pointer-p t
+ :sync-keyboard-p nil)))
+
+
+(defun xwin-ungrab-buttons (win)
+ (xlib:ungrab-button win :any :modifiers :any))
+
+(defun sync-keys ()
+ "Any time *top-map* is modified this must be called."
+ (loop for i in *screen-list*
+ do (xwin-ungrab-keys (screen-focus-window i))
+ do (loop for j in (screen-mapped-windows i)
+ do (xwin-ungrab-keys j))
+ do (xlib:display-finish-output *display*)
+ do (loop for j in (screen-mapped-windows i)
+ do (xwin-grab-keys j))
+ do (xwin-grab-keys (screen-focus-window i)))
+ (xlib:display-finish-output *display*))
+
+
+;;; Window placement routines
+
+(defun xwin-to-window (xwin)
+ "Build a window for XWIN"
+ (make-window
+ :xwin xwin
+ :width (xlib:drawable-width xwin) :height (xlib:drawable-height xwin)
+ :x (xlib:drawable-x xwin) :y (xlib:drawable-y xwin)
+ :title (xwin-name xwin)
+ :class (xwin-class xwin)
+ :res (xwin-res-name xwin)
+ :role (xwin-role xwin)
+ :type (xwin-type xwin)
+ :normal-hints (xlib:wm-normal-hints xwin)
+ :state +iconic-state+
+ :plist (make-hash-table)
+ :unmap-ignores 0))
+
+(defun string-match (string pat)
+ (let ((l (length pat)))
+ (when (> l 0)
+ (if (and (> l 3) (equal (subseq pat 0 3) "..."))
+ (search (subseq pat 3 l) string)
+ (equal string pat)))))
+
+(defun window-matches-properties-p (window &key class instance type role title)
+ "Returns T if window matches all the given properties"
+ (and
+ (if class (equal (window-class window) class) t)
+ (if instance (equal (window-res window) instance) t)
+ (if type (equal (window-type window) type) t)
+ (if role (string-match (window-role window) role) t)
+ (if title (string-match (window-title window) title) t) t))
+
+(defun window-matches-rule-p (w rule)
+ "Returns T if window matches rule"
+ (destructuring-bind (group-name frame raise lock &rest props) rule
+ (declare (ignore frame raise))
+ (if (or lock
+ (equal group-name (group-name (or (window-group w) (current-group)))))
+ (apply 'window-matches-properties-p w props))))
+
+;; TODO: add rules allowing matched windows to create their own groups/frames
+
+(defun rule-matching-window (window)
+ (dolist (rule *window-placement-rules*)
+ (when (window-matches-rule-p window rule) (return rule))))
+
+(defun get-window-placement (screen window)
+ "Returns the ideal group and frame that WINDOW should belong to and whether
+ the window should be raised."
+ (let ((match (rule-matching-window window)))
+ (if match
+ (destructuring-bind (group-name frame raise lock &rest props) match
+ (declare (ignore lock props))
+ (let ((group (find-group screen group-name)))
+ (if group
+ (values group (frame-by-number group frame) raise)
+ (progn
+ (message "^B^1*Error placing window, group \"^b~a^B\" does not exist." group-name)
+ (values)))))
+ (values))))
+
+(defun sync-window-placement ()
+ "Re-arrange existing windows according to placement rules"
+ (dolist (screen *screen-list*)
+ (dolist (window (screen-windows screen))
+ (multiple-value-bind (to-group frame raise) (get-window-placement screen window)
+ (declare (ignore raise))
+ (when to-group
+ (unless (eq (window-group window) to-group)
+ (move-window-to-group window to-group))
+ (unless (eq (window-frame window) frame)
+ (pull-window window frame)))))))
+
+(defun assign-window (window group frame &optional (where :tail))
+ (setf (window-group window) group
+ (window-number window) (find-free-window-number group)
+ (window-frame window) (or frame (pick-prefered-frame window)))
+ (if (eq where :head)
+ (push window (group-windows group))
+ (setf (group-windows group) (append (group-windows group) (list window)))))
+
+(defun place-existing-window (screen xwin)
+ "Called for windows existing at startup."
+ (let* ((window (xwin-to-window xwin))
+ (netwm-id (first (xlib:get-property xwin :_NET_WM_DESKTOP)))
+ (group (if (and netwm-id (< netwm-id (length (screen-groups screen))))
+ (elt (sort-groups screen) netwm-id)
+ (screen-current-group screen))))
+ (dformat 3 "Assigning pre-existing window ~S to group ~S~%" (window-name window) (group-name group))
+ (assign-window window group (find-frame group (xlib:drawable-x xwin) (xlib:drawable-y xwin)) :head)
+ (setf (frame-window (window-frame window)) window)
+ window))
+
+(defun place-window (screen xwin)
+ "Pick a group and frame for XWIN."
+ (let* ((window (xwin-to-window xwin))
+ (group (screen-current-group screen))
+ (frame nil)
+ (raise nil))
+ (multiple-value-bind (to-group to-frame to-raise) (get-window-placement screen window)
+ (setf group (or to-group group)
+ frame to-frame
+ raise to-raise))
+ (assign-window window group frame)
+ (setf (xwin-state xwin) +iconic-state+)
+ (xlib:change-property xwin :_NET_WM_DESKTOP
+ (list (netwm-group-id group))
+ :cardinal 32)
+ (when frame
+ (unless (eq (current-group) group)
+ (if raise
+ (switch-to-group group)
+ (message "Placing window ~a in frame ~d of group ~a"
+ (window-name window) (frame-number frame) (group-name group))))
+ (when raise
+ (switch-to-screen (group-screen group))
+ (focus-frame group frame))
+ (run-hook-with-args *place-window-hook* window group frame))
+ window))
+
+(defun pick-prefered-frame (window)
+ (let* ((group (window-group window))
+ (frames (group-frames group))
+ (default (tile-group-current-frame group)))
+ (or
+ (if (or (functionp *new-window-prefered-frame*)
+ (and (symbolp *new-window-prefered-frame*)
+ (fboundp *new-window-prefered-frame*)))
+ (handler-case
+ (funcall *new-window-prefered-frame* window)
+ (error (c)
+ (message "^1*^BError while calling ^b^3**new-window-prefered-frame*^1*^B: ^n~a" c)
+ nil))
+ (loop for i in *new-window-prefered-frame*
+ thereis (case i
+ (:last
+ ;; last-frame can be stale
+ (and (> (length frames) 1)
+ (tile-group-last-frame group)))
+ (:unfocused
+ (find-if (lambda (f)
+ (not (eq f (tile-group-current-frame group))))
+ frames))
+ (:empty
+ (find-if (lambda (f)
+ (null (frame-window f)))
+ frames))
+ (:choice
+ ;; Transient windows sometimes specify a location
+ ;; relative to the TRANSIENT_FOR window. Just ignore
+ ;; these hints.
+ (unless (find (window-type window) '(:transient :dialog))
+ (let ((hints (window-normal-hints window)))
+ (when (and hints (xlib:wm-size-hints-user-specified-position-p hints))
+ (find-frame group (window-x window) (window-y window))))))
+ (t ; :focused
+ (tile-group-current-frame group)))))
+ default)))
+
+(defun add-window (screen xwin)
+ (screen-add-mapped-window screen xwin)
+ (register-window (if *processing-existing-windows*
+ (place-existing-window screen xwin)
+ (place-window screen xwin))))
+
+(defun netwm-remove-window (window)
+ (xlib:delete-property (window-xwin window) :_NET_WM_DESKTOP))
+
+(defun process-mapped-window (screen xwin)
+ "Add the window to the screen's mapped window list and process it as
+needed."
+ (let ((window (add-window screen xwin)))
+ (setf (xlib:window-event-mask (window-xwin window)) *window-events*)
+ ;; windows always have border width 0. Their parents provide the
+ ;; border.
+ (set-window-geometry window :border-width 0)
+ (reparent-window window)
+ (maximize-window window)
+ (grab-keys-on-window window)
+ ;; quite often the modeline displays the window list, so update it
+ (update-all-mode-lines)
+ ;; Set allowed actions
+ (xlib:change-property xwin :_NET_WM_ALLOWED_ACTIONS
+ (mapcar (lambda (a)
+ (xlib:intern-atom *display* a))
+ +netwm-allowed-actions+)
+ :atom 32)
+ ;; Run the new window hook on it.
+ (run-hook-with-args *new-window-hook* window)
+ window))
+
+(defun find-withdrawn-window (xwin)
+ "Return the window and screen for a withdrawn window."
+ (declare (type xlib:window xwin))
+ (dolist (i *screen-list*)
+ (let ((w (find xwin (screen-withdrawn-windows i) :key 'window-xwin :test 'xlib:window-equal)))
+ (when w
+ (return-from find-withdrawn-window (values w i))))))
+
+(defun restore-window (window)
+ "Restore a withdrawn window"
+ (declare (type window window))
+ ;; put it in a valid group
+ (let ((screen (window-screen window)))
+ ;; Use window plaecment rules
+ (multiple-value-bind (group frame raise) (get-window-placement screen window)
+ (declare (ignore raise))
+ (unless (find (window-group window)
+ (screen-groups screen))
+ (setf (window-group window) (or group (screen-current-group screen))))
+ ;; FIXME: somehow it feels like this could be merged with group-add-window
+ (setf (window-title window) (xwin-name (window-xwin window))
+ (window-class window) (xwin-class (window-xwin window))
+ (window-res window) (xwin-res-name (window-xwin window))
+ (window-role window) (xwin-role (window-xwin window))
+ (window-type window) (xwin-type (window-xwin window))
+ (window-normal-hints window) (xlib:wm-normal-hints (window-xwin window))
+ (window-number window) (find-free-window-number (window-group window))
+ (window-state window) +iconic-state+
+ (xwin-state (window-xwin window)) +iconic-state+
+ (screen-withdrawn-windows screen) (delete window (screen-withdrawn-windows screen))
+ ;; put the window at the end of the list
+ (group-windows (window-group window)) (append (group-windows (window-group window)) (list window))
+ (window-frame window) (or frame (pick-prefered-frame window))))
+ (screen-add-mapped-window screen (window-xwin window))
+ (register-window window)
+ (xlib:change-property (window-xwin window) :_NET_WM_DESKTOP
+ (list (netwm-group-id (window-group window)))
+ :cardinal 32)
+ (maximize-window window)
+ ;; It is effectively a new window in terms of the window list.
+ (run-hook-with-args *new-window-hook* window)
+ ;; give it focus
+ (if (deny-request-p window *deny-map-request*)
+ (unless *suppress-deny-messages*
+ (if (eq (window-group window) (current-group))
+ (echo-string (window-screen window) (format nil "'~a' denied map request" (window-name window)))
+ (echo-string (window-screen window) (format nil "'~a' denied map request in group ~a" (window-name window) (group-name (window-group window))))))
+ (frame-raise-window (window-group window) (window-frame window) window
+ (if (eq (window-frame window)
+ (tile-group-current-frame (window-group window)))
+ t nil)))))
+
+(defun withdraw-window (window)
+ "Withdrawing a window means just putting it in a list til we get a destroy event."
+ (declare (type window window))
+ ;; This function cannot request info about WINDOW from the xserver as it may not exist anymore.
+ (let ((f (window-frame window))
+ (group (window-group window))
+ (screen (window-screen window)))
+ (dformat 1 "withdraw window ~a~%" screen)
+ ;; Save it for later since it is only withdrawn, not destroyed.
+ (push window (screen-withdrawn-windows screen))
+ (setf (window-state window) +withdrawn-state+
+ (xwin-state (window-xwin window)) +withdrawn-state+)
+ (xlib:unmap-window (window-parent window))
+ ;; Clean up the window's entry in the screen and group
+ (screen-remove-mapped-window screen (window-xwin window))
+ (setf (group-windows group)
+ (delete window (group-windows group)))
+ ;; remove it from it's frame structures
+ (when (eq (frame-window f) window)
+ (frame-raise-window group f (first (frame-windows group f)) nil))
+ (when (window-in-current-group-p window)
+ ;; since the window doesn't exist, it doesn't have focus.
+ (setf (screen-focus screen) nil))
+ (netwm-remove-window window)
+ ;; If the current window was removed, then refocus the frame it
+ ;; was in, since it has a new current window
+ (when (eq (tile-group-current-frame group) f)
+ (focus-frame (window-group window) f))
+ ;; quite often the modeline displays the window list, so update it
+ (update-all-mode-lines)
+ ;; Run the destroy hook on the window
+ (run-hook-with-args *destroy-window-hook* window)))
+
+(defun destroy-window (window)
+ (declare (type window window))
+ "The window has been destroyed. clean up our data structures."
+ ;; This function cannot request info about WINDOW from the xserver
+ (let ((screen (window-screen window)))
+ (unless (eql (window-state window) +withdrawn-state+)
+ (withdraw-window window))
+ ;; now that the window is withdrawn, clean up the data structures
+ (setf (screen-withdrawn-windows screen)
+ (delete window (screen-withdrawn-windows screen)))
+ (dformat 1 "destroy window ~a~%" screen)
+ (dformat 3 "destroying parent window~%")
+ (xlib:destroy-window (window-parent window))))
+
+(defun move-window-to-head (group window)
+ "Move window to the head of the group's window list."
+ (declare (type group group))
+ (declare (type window window))
+ ;(assert (member window (screen-mapped-windows screen)))
+ (setf (group-windows group) (delete window (group-windows group)))
+ (push window (group-windows group))
+ (netwm-update-client-list-stacking (group-screen group)))
+
+(defun no-focus (group last-win)
+ "don't focus any window but still read keyboard events."
+ (dformat 3 "no-focus~%")
+ (let* ((screen (group-screen group)))
+ (when (eq group (screen-current-group screen))
+ (xlib:set-input-focus *display* (screen-focus-window screen) :POINTER-ROOT)
+ (setf (screen-focus screen) nil)
+ (move-screen-to-head screen))
+ (when last-win
+ (update-window-border last-win))))
+
+(defun focus-window (window)
+ "Give the window focus. This means the window will be visible,
+maximized, and given focus."
+ (dformat 3 "focus-window: ~s~%" window)
+ (let* ((group (window-group window))
+ (screen (group-screen group))
+ (cw (screen-focus screen)))
+ ;; If window to focus is already focused then our work is done.
+ (unless (eq window cw)
+ (update-all-mode-lines)
+ (raise-window window)
+ (screen-set-focus screen window)
+ ;;(send-client-message window :WM_PROTOCOLS +wm-take-focus+)
+ (update-window-border window)
+ (when cw
+ (update-window-border cw))
+ ;; Move the window to the head of the mapped-windows list
+ (move-window-to-head group window)
+ (run-hook-with-args *focus-window-hook* window cw))))
+
+(defun delete-window (window)
+ "Send a delete event to the window."
+ (dformat 3 "Delete window~%")
+ (send-client-message window :WM_PROTOCOLS (xlib:intern-atom *display* :WM_DELETE_WINDOW)))
+
+(defun xwin-kill (window)
+ "Kill the client associated with window."
+ (dformat 3 "Kill client~%")
+ (xlib:kill-client *display* (xlib:window-id window)))