mirror of
https://github.com/stumpwm/stumpwm.git
synced 2026-09-10 07:26:20 -04:00
442 lines
19 KiB
Common Lisp
442 lines
19 KiB
Common Lisp
;;; implementation of a floating style window management group
|
|
|
|
(in-package #:stumpwm)
|
|
|
|
;;; floating window
|
|
|
|
(define-swm-class float-window (window)
|
|
((last-width :initform 0 :accessor float-window-last-width)
|
|
(last-height :initform 0 :accessor float-window-last-height)
|
|
(last-x :initform 0 :accessor float-window-last-x)
|
|
(last-y :initform 0 :accessor float-window-last-y)))
|
|
|
|
(defmethod print-swm-object ((object float-window) stream)
|
|
(write-string "FLOAT-" stream)
|
|
(call-next-method))
|
|
|
|
(defvar *float-window-border* 1)
|
|
(defvar *float-window-title-height* 10)
|
|
(defvar *float-window-modifier* :super
|
|
"The keyboard modifier to use for resize and move floating windows without
|
|
clicking on the top border. Valid values are :META :ALT :HYPER :SUPER, :ALTGR
|
|
and :NUMLOCK.")
|
|
|
|
|
|
(defun float-window-p (window)
|
|
(typep window 'float-window))
|
|
|
|
(defun float-window-modifier ()
|
|
"Convert the *FLOAT-WINDOW-MODIFIER* to its corresponding X11."
|
|
(when-let ((fn (find-symbol (concat "MODIFIERS-" (symbol-name *float-window-modifier*))
|
|
(find-package "STUMPWM"))))
|
|
(funcall fn *modifiers*)))
|
|
|
|
;; some book keeping functions
|
|
(defmethod (setf window-x) :before (val (window float-window))
|
|
(setf (float-window-last-x window) (window-x window)))
|
|
|
|
(defmethod (setf window-y) :before (val (window float-window))
|
|
(setf (float-window-last-y window) (window-y window)))
|
|
|
|
(defmethod (setf window-width) :before (val (window float-window))
|
|
(setf (float-window-last-width window) (window-width window)))
|
|
|
|
(defmethod (setf window-height) :before (val (window float-window))
|
|
(setf (float-window-last-height window) (window-height window)))
|
|
|
|
(defun float-window-move-resize (win &key x y width height (border *float-window-border*))
|
|
;; x and y position the parent window while width, height resize the
|
|
;; xwin (meaning the parent will have a larger width).
|
|
(with-accessors ((xwin window-xwin)
|
|
(parent window-parent))
|
|
win
|
|
(xlib:with-state (parent)
|
|
(xlib:with-state (xwin)
|
|
(when x
|
|
(setf (xlib:drawable-x parent) x
|
|
(window-x win) x))
|
|
(when y
|
|
(setf (xlib:drawable-y parent) y
|
|
(window-y win) y))
|
|
(when width
|
|
(setf (xlib:drawable-width parent) (+ (xlib:drawable-x xwin) width border)
|
|
(xlib:drawable-width xwin) width
|
|
(window-width win) width))
|
|
(when height
|
|
(setf (xlib:drawable-height parent) (+ (xlib:drawable-y xwin) height border)
|
|
(xlib:drawable-height xwin) height
|
|
(window-height win) height))))))
|
|
|
|
(defmethod update-decoration ((window float-window))
|
|
(let ((group (window-group window)))
|
|
(setf (xlib:window-background (window-parent window))
|
|
(if (eq (group-current-window group) window)
|
|
(screen-float-focus-color (window-screen window))
|
|
(screen-float-unfocus-color (window-screen window))))
|
|
(xlib:clear-area (window-parent window))))
|
|
|
|
(defmethod window-sync ((window float-window) hint)
|
|
(declare (ignore hint)))
|
|
|
|
(defmethod window-head ((window float-window))
|
|
(let ((left (window-x window))
|
|
(right (+ (window-x window) (window-width window)))
|
|
(top (window-y window))
|
|
(bottom (+ (window-y window) (window-height window)))
|
|
(heads (screen-heads (group-screen (window-group window)))))
|
|
(flet ((within-frame-p (y x head)
|
|
(and (>= x (frame-x head))
|
|
(< x (+ (frame-x head) (1- (frame-width head))))
|
|
(>= y (frame-y head))
|
|
(< y (+ (frame-y head) (1- (frame-height head)))))))
|
|
(or (find-if (lambda (head)
|
|
(or (within-frame-p top left head)
|
|
(within-frame-p top right head)
|
|
(within-frame-p bottom left head)
|
|
(within-frame-p bottom right head)))
|
|
heads)
|
|
;; Didn't find any head, so give up and return the first one
|
|
;; in the list.
|
|
(first heads)))))
|
|
|
|
(defmethod window-visible-p ((win float-window))
|
|
(eql (window-state win) +normal-state+))
|
|
|
|
(defmethod (setf window-fullscreen) :after (val (window float-window))
|
|
(with-accessors ((last-x float-window-last-x)
|
|
(last-y float-window-last-y)
|
|
(last-width float-window-last-width)
|
|
(last-height float-window-last-height)
|
|
(parent window-parent))
|
|
window
|
|
(if val
|
|
(let ((head (window-head window)))
|
|
(with-accessors ((x window-x)
|
|
(y window-y)
|
|
(width window-width)
|
|
(height window-height))
|
|
window
|
|
(format t "major on: ~a ~a ~a ~a~%" x y width height))
|
|
(set-window-geometry window :x 0 :y 0)
|
|
(float-window-move-resize window
|
|
:x (frame-x head)
|
|
:y (frame-y head)
|
|
:width (frame-width head)
|
|
:height (frame-height head)
|
|
:border 0)
|
|
(format t "loot after: ~a ~a ~a ~a~%" last-x last-y last-width last-height))
|
|
(progn
|
|
(format t "fullscreenage: ~a ~a ~a ~a~%" last-x last-y last-width last-height)
|
|
;; restore the position
|
|
(set-window-geometry window :x *float-window-border* :y *float-window-title-height*)
|
|
(float-window-move-resize window
|
|
:x last-x
|
|
:y last-y
|
|
:width last-width
|
|
:height last-height)))))
|
|
|
|
(defmethod really-raise-window ((window float-window))
|
|
(raise-window window))
|
|
|
|
;;; floating group
|
|
|
|
(define-swm-class float-group (group)
|
|
((current-window :accessor float-group-current-window)))
|
|
|
|
(defmethod print-swm-object ((object float-group) stream)
|
|
(write-string "FLOAT-" stream)
|
|
(call-next-method))
|
|
|
|
(defmethod group-startup ((group float-group)))
|
|
|
|
(flet ((add-float-window (group window raise)
|
|
(dynamic-mixins:replace-class window 'float-window)
|
|
;; (change-class window 'float-window)
|
|
(float-window-align window)
|
|
(sync-minor-modes window)
|
|
(when raise
|
|
(group-focus-window group window))))
|
|
(defmethod group-add-window ((group float-group) window &key raise &allow-other-keys)
|
|
(add-float-window group window raise))
|
|
(defmethod group-add-window (group (window float-window) &key raise &allow-other-keys)
|
|
(add-float-window group window raise)))
|
|
|
|
(defun %float-focus-next (group)
|
|
(let ((windows (remove-if 'window-hidden-p (group-windows group))))
|
|
(if windows
|
|
(group-focus-window group (first windows))
|
|
(no-focus group nil))))
|
|
|
|
(defmethod group-delete-window ((group float-group) (window float-window))
|
|
(declare (ignore window))
|
|
(%float-focus-next group))
|
|
|
|
(defmethod group-wake-up ((group float-group))
|
|
(%float-focus-next group))
|
|
|
|
(defmethod group-suspend ((group float-group)))
|
|
|
|
(defmethod group-current-head ((group float-group))
|
|
(if-let ((current-window (group-current-window group)))
|
|
(window-head current-window)
|
|
(multiple-value-bind (x y)
|
|
(xlib:global-pointer-position *display*)
|
|
(find-head-by-position (group-screen group) x y))))
|
|
|
|
(defun float-window-align (window)
|
|
(with-accessors ((parent window-parent)
|
|
(screen window-screen)
|
|
(width window-width)
|
|
(height window-height))
|
|
window
|
|
(set-window-geometry window :x *float-window-border* :y *float-window-title-height*)
|
|
(xlib:with-state (parent)
|
|
(setf (xlib:drawable-width parent) (+ width (* 2 *float-window-border*))
|
|
(xlib:drawable-height parent) (+ height *float-window-title-height* *float-window-border*)
|
|
(xlib:window-background parent) (xlib:alloc-color (xlib:screen-default-colormap (screen-number (window-screen window)))
|
|
"Orange")))
|
|
(xlib:clear-area (window-parent window))
|
|
(let ((parent-x (xlib:drawable-x parent))
|
|
(parent-y (xlib:drawable-y parent))
|
|
(parent-width (xlib:drawable-width parent))
|
|
(parent-height (xlib:drawable-height parent))
|
|
(border (xlib:drawable-border-width parent))
|
|
(screen-width (screen-width screen))
|
|
(screen-height (screen-height screen)))
|
|
;; Resize window when borders outside screen
|
|
(let ((diff-width (- (+ parent-x parent-width) (- screen-width (* 2 border))))
|
|
(diff-height (- (+ parent-y parent-height) (- screen-height (* 2 border)))))
|
|
(when (or (> parent-x 0) (> parent-y 0))
|
|
(float-window-move-resize window :x parent-x :y parent-y))
|
|
(when (> diff-width 0)
|
|
(float-window-move-resize window :width (- width diff-width)))
|
|
(when (> diff-height 0)
|
|
(float-window-move-resize window :height (- height diff-height)))))))
|
|
|
|
(defmethod group-resize-request ((group float-group) window width height)
|
|
(float-window-move-resize window :width width :height height))
|
|
|
|
(defmethod group-move-request ((group float-group) window x y relative-to)
|
|
(declare (ignore relative-to))
|
|
(float-window-move-resize window :x x :y y))
|
|
|
|
(defmethod group-raise-request ((group float-group) window type)
|
|
(declare (ignore type))
|
|
(group-focus-window group window))
|
|
|
|
(defmethod group-lost-focus ((group float-group))
|
|
(%float-focus-next group))
|
|
|
|
(defmethod group-indicate-focus ((group float-group)))
|
|
|
|
(defmethod group-focus-window ((group float-group) window)
|
|
(focus-window window))
|
|
|
|
(defmethod group-root-exposure ((group float-group)))
|
|
|
|
(defmethod group-add-head ((group float-group) head)
|
|
(declare (ignore head)))
|
|
|
|
(defmethod group-remove-head ((group float-group) head)
|
|
(declare (ignore head)))
|
|
|
|
(defmethod group-replace-head (screen (group float-group) old-head new-head)
|
|
(declare (ignore screen old-head new-head)))
|
|
|
|
(defmethod group-before-resize-head ((group float-group) oh nh)
|
|
(declare (ignore oh nh)))
|
|
|
|
(defmethod group-after-resize-head ((group float-group) head)
|
|
(declare (ignore head)))
|
|
|
|
(defmethod group-sync-all-heads ((group float-group)))
|
|
|
|
(defmethod group-sync-head ((group float-group) head)
|
|
(declare (ignore head)))
|
|
|
|
(defmethod group-adopt-orphaned-windows ((group float-group) &optional (screen (current-screen)))
|
|
(let ((orphaned-frames (orphaned-frames screen)))
|
|
(loop for window in (list-windows screen)
|
|
when (member (window-frame window) orphaned-frames)
|
|
do (group-add-window group window))))
|
|
|
|
(defvar *last-click-time* 0
|
|
"Time since the last click occurred")
|
|
|
|
(defun window-display-height (window)
|
|
"Returns maximum displayable height of window accounting for the mode-line"
|
|
(let* ((head (window-head window))
|
|
(ml (head-mode-line head))
|
|
(ml-height (if (null ml) 0 (mode-line-height ml))))
|
|
(- (head-height head) ml-height
|
|
(* 2 *normal-border-width*)
|
|
*float-window-border*
|
|
*float-window-title-height*)))
|
|
|
|
(defun maximize-float (window &key horizontal vertical)
|
|
(let* ((head (window-head window))
|
|
(ml (head-mode-line head))
|
|
(hx (head-x head))
|
|
(hy (if (null ml) 0 (mode-line-height ml)))
|
|
(w (- (head-width head)
|
|
(* 2 *normal-border-width*)
|
|
(* 2 *float-window-border*)))
|
|
(h (window-display-height window)))
|
|
(when horizontal
|
|
(float-window-move-resize window :width w))
|
|
(when vertical
|
|
(float-window-move-resize window :y hy :height h))
|
|
(when (and horizontal vertical)
|
|
(float-window-move-resize window :x hx :y hy))))
|
|
|
|
(defmethod group-button-press (group button x y (window float-window))
|
|
(declare (ignore button))
|
|
(let ((screen (group-screen group))
|
|
(initial-width (xlib:drawable-width (window-parent window)))
|
|
(initial-height (xlib:drawable-height (window-parent window)))
|
|
(initial-x (xlib:drawable-x (window-parent window)))
|
|
(initial-y (xlib:drawable-y (window-parent window)))
|
|
(xwin (window-xwin window)))
|
|
(when (member *mouse-focus-policy* '(:click :sloppy))
|
|
(group-focus-window group window))
|
|
|
|
;; When in border
|
|
(multiple-value-bind (relx rely same-screen-p child state-mask)
|
|
(xlib:query-pointer (window-parent window))
|
|
(declare (ignore same-screen-p child))
|
|
(when (or (< x (xlib:drawable-x xwin))
|
|
(> x (+ (xlib:drawable-width xwin)
|
|
(xlib:drawable-x xwin)))
|
|
(< y (xlib:drawable-y xwin))
|
|
(> y (+ (xlib:drawable-height xwin)
|
|
(xlib:drawable-y xwin)))
|
|
(intersection (float-window-modifier)
|
|
(xlib:make-state-keys state-mask)))
|
|
(when (find :button-1 (xlib:make-state-keys state-mask))
|
|
(let* ((current-time (/ (get-internal-real-time)
|
|
internal-time-units-per-second))
|
|
(delta-t (- current-time *last-click-time*))
|
|
(win-focused-p (eq window (screen-focus screen))))
|
|
(setf *last-click-time* current-time)
|
|
(when (< delta-t 0.25)
|
|
(cond ((and (not (eq (window-height window)
|
|
(window-display-height window)))
|
|
win-focused-p)
|
|
(maximize-float window :vertical t))
|
|
(win-focused-p (maximize-float window :vertical t :horizontal t))
|
|
(t (focus-window window t))))))
|
|
|
|
(multiple-value-bind (relx rely same-screen-p child state-mask)
|
|
(xlib:query-pointer (window-parent window))
|
|
(declare (ignore same-screen-p child))
|
|
|
|
(let ((left-quadrant (< relx (floor initial-width 2)))
|
|
(top-quadrant (< rely (floor initial-height 2))))
|
|
(dformat 4 "corner: left: ~a top: ~a~%" left-quadrant top-quadrant)
|
|
|
|
;; When resizing warp pointer to closest corner
|
|
(when (find :button-3 (xlib:make-state-keys state-mask))
|
|
(xlib:warp-pointer (window-parent window)
|
|
(if left-quadrant 0 initial-width)
|
|
(if top-quadrant 0 initial-height)))
|
|
|
|
(labels ((move-window-event-handler
|
|
(&rest event-slots &key event-key &allow-other-keys)
|
|
(case event-key
|
|
(:button-release :done)
|
|
(:motion-notify
|
|
(with-accessors ((parent window-parent))
|
|
window
|
|
(xlib:with-state (parent)
|
|
;; Either move or resize the window
|
|
(cond
|
|
((find :button-1 (xlib:make-state-keys state-mask))
|
|
;; if button-1 on the sides (left,
|
|
;; right, bottom) then we resize that
|
|
;; direction
|
|
|
|
;; if button-1 on the top, then we move the window
|
|
(float-window-move-resize window
|
|
:x (- (getf event-slots :x) relx)
|
|
:y (- (getf event-slots :y) rely)))
|
|
((find :button-3 (xlib:make-state-keys state-mask))
|
|
(let ((w (if left-quadrant
|
|
(- initial-width
|
|
(- (getf event-slots :x)
|
|
initial-x))
|
|
(- (getf event-slots :x)
|
|
(xlib:drawable-x parent))))
|
|
(h (if top-quadrant
|
|
(- initial-height
|
|
(- (getf event-slots :y)
|
|
initial-y))
|
|
(- (getf event-slots :y)
|
|
(xlib:drawable-y parent)
|
|
*float-window-title-height*)))
|
|
;; also move window when in top and/or left quadrant
|
|
(x (if left-quadrant
|
|
(getf event-slots :x)
|
|
initial-x))
|
|
(y (if top-quadrant
|
|
(getf event-slots :y)
|
|
initial-y)))
|
|
|
|
;; Don't let the window become too small
|
|
(float-window-move-resize window
|
|
:x x
|
|
:y y
|
|
:width (max w *min-frame-width*)
|
|
:height (max h *min-frame-height*)))))))
|
|
t)
|
|
;; We need to eat these events or they'll ALL
|
|
;; come blasting in later. Also things start
|
|
;; lagging hard if we don't (on clisp anyway).
|
|
(:configure-notify t)
|
|
(:exposure t)
|
|
(t nil))))
|
|
(xlib:grab-pointer (screen-root screen) '(:button-release :pointer-motion))
|
|
(unwind-protect
|
|
;; Wait until the mouse button is released
|
|
(loop for ev = (xlib:process-event *display*
|
|
:handler #'move-window-event-handler
|
|
:timeout nil
|
|
:discard-p t)
|
|
until (eq ev :done))
|
|
(ungrab-pointer))
|
|
(update-configuration window)
|
|
;; don't forget to update the cache
|
|
(setf (window-x window) (xlib:drawable-x (window-parent window))
|
|
(window-y window) (xlib:drawable-y (window-parent window)))))))
|
|
;; restore the mouse to its original position
|
|
(xlib:warp-pointer (window-parent window) relx rely))))
|
|
|
|
(defmethod group-button-press ((group float-group) button x y where)
|
|
(declare (ignore button x y where))
|
|
(when (next-method-p)
|
|
(call-next-method)))
|
|
|
|
;;; Bindings
|
|
|
|
(defvar *float-group-top-map* nil)
|
|
(defvar *float-group-root-map* nil
|
|
"Commands specific to a floating group context hang from this keymap.
|
|
It is available as part of the @dnf{prefix map} when the active group
|
|
is a float group.")
|
|
|
|
(fill-keymap *float-group-top-map*
|
|
*escape-key* '*float-group-root-map*)
|
|
|
|
(fill-keymap *float-group-root-map*
|
|
(kbd "n") "next"
|
|
(kbd "p") "prev")
|
|
|
|
(pushnew '(float-group *float-group-top-map*) *group-top-maps*)
|
|
|
|
(defcommand gnew-float (name) ((:rest "Group name: "))
|
|
"Create a floating window group with the specified name and switch to it."
|
|
(add-group (current-screen) name :type 'float-group))
|
|
|
|
(defcommand gnewbg-float (name) ((:rest "Group name: "))
|
|
"Create a floating window group with the specified name, but do not switch to it."
|
|
(add-group (current-screen) name :background t :type 'float-group))
|