diff --git a/Makefile.in b/Makefile.in index ff09b91e..330598be 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,9 +1,9 @@ LISP=@LISP_PROGRAM@ MAKEINFO=@MAKEINFO@ -sbcl_BUILDOPTS=--non-interactive --eval "(setf sb-impl::*default-external-format* :UTF-8)" --load ./make-image.lisp -sbcl_INFOOPTS=--non-interactive --eval "(setf sb-impl::*default-external-format* :UTF-8)" --eval "(progn (load \"load-stumpwm.lisp\") (load \"manual.lisp\"))" --eval "(progn (stumpwm::generate-manual) (sb-ext:quit))" -sbcl_TESTOPTS=--non-interactive --eval "(setf sb-impl::*default-external-format* :UTF-8)" --eval "(progn (load \"load-stumpwm.lisp\") (asdf:load-system :stumpwm-tests))" --eval "(if (fiasco:all-tests) (uiop:quit 0) (uiop:quit 1))" +sbcl_BUILDOPTS=--non-interactive --eval "(require 'asdf)" --eval "(setf sb-impl::*default-external-format* :UTF-8)" --load ./make-image.lisp +sbcl_INFOOPTS=--non-interactive --eval "(require 'asdf)" --eval "(setf sb-impl::*default-external-format* :UTF-8)" --eval "(progn (load \"load-stumpwm.lisp\") (load \"manual.lisp\"))" --eval "(progn (stumpwm::generate-manual) (sb-ext:quit))" +sbcl_TESTOPTS=--non-interactive --eval "(require 'asdf)" --eval "(setf sb-impl::*default-external-format* :UTF-8)" --eval "(progn (load \"load-stumpwm.lisp\") (asdf:load-system :stumpwm/tests))" --eval "(if (fiasco:all-tests) (uiop:quit 0) (uiop:quit 1))" datarootdir = @datarootdir@ prefix=@prefix@ diff --git a/input.lisp b/input.lisp index 6d05a17f..97cb4c68 100644 --- a/input.lisp +++ b/input.lisp @@ -25,6 +25,7 @@ (export '(*input-history-ignore-duplicates* *input-candidate-selected-hook* + *input-refine-candidates-fn* *input-completion-style* *input-map* *numpad-map* @@ -36,6 +37,7 @@ input-insert-string input-point input-refine-prefix + input-refine-fuzzy input-refine-regexp input-substring input-validate-region @@ -136,6 +138,16 @@ and complete the input by mutating it.")) :end2 (length str)))) candidates)) +(defun input-refine-fuzzy (str candidates) + (remove-if-not (lambda (elt) + (when (listp elt) + (setf elt (car elt))) + (and (<= (length str) (length elt)) + (every (lambda (part) + (search part elt)) + (uiop:split-string str)))) + candidates)) + (defun input-refine-regexp (str candidates) (remove-if-not (lambda (elt) (when (listp elt) @@ -146,7 +158,9 @@ and complete the input by mutating it.")) (defvar *input-map* (let ((map (make-sparse-keymap))) (define-key map (kbd "DEL") 'input-delete-backward-char) + (define-key map (kbd "S-DEL") 'input-delete-backward-char) (define-key map (kbd "M-DEL") 'input-backward-kill-word) + (define-key map (kbd "C-DEL") 'input-backward-kill-word) (define-key map (kbd "C-d") 'input-delete-forward-char) (define-key map (kbd "M-d") 'input-forward-kill-word) (define-key map (kbd "Delete") 'input-delete-forward-char) diff --git a/load-stumpwm.lisp.in b/load-stumpwm.lisp.in index a7b228a3..0c0e2f94 100644 --- a/load-stumpwm.lisp.in +++ b/load-stumpwm.lisp.in @@ -5,9 +5,5 @@ (require 'asdf) -(asdf:initialize-source-registry - '(:source-registry - (:directory "@STUMPWM_ASDF_DIR@") - :inherit-configuration)) - -(asdf:oos 'asdf:load-op 'stumpwm) +(asdf:load-asd #p"@STUMPWM_ASDF_DIR@/stumpwm.asd") +(asdf:load-system "stumpwm") diff --git a/make-image.lisp.in b/make-image.lisp.in index f0b648c6..2f4d263b 100644 --- a/make-image.lisp.in +++ b/make-image.lisp.in @@ -1,3 +1,5 @@ +(require :uiop) + (in-package #:cl-user) (let* ((expected-warnings @@ -48,7 +50,17 @@ (uiop:symbol-call '#:asdf '#:register-immutable-system system-name))) (sb-ext:save-lisp-and-die "stumpwm" :toplevel (lambda () - ;; asdf requires sbcl_home to be set, so set it to the value when the image was built + ;; stumpwm might be built in a fake enviroment, so use uiop:restore-image + ;; to compute the real uiop:*user-cache* for that user + (uiop:restore-image) + ;; and clean the asdf configuration to avoid some cached paths as well + (asdf:clear-configuration) + (asdf:clear-output-translations) + (asdf:initialize-output-translations + '(:output-translations + :enable-user-cache + :ignore-inherited-configuration)) + ;; asdf requires SBCL_HOME to be set, so set it to the value when the image was built (alexandria:when-let ((home #.(sb-ext:posix-getenv "SBCL_HOME"))) (sb-posix:putenv (format nil "SBCL_HOME=~A" home))) (stumpwm:stumpwm) diff --git a/message-window.lisp b/message-window.lisp index 2a46dc54..2bc1b373 100644 --- a/message-window.lisp +++ b/message-window.lisp @@ -82,10 +82,16 @@ function expects to be wrapped in a with-state for win." (* (xlib:drawable-border-width win) 2))) (head-x (head-x (current-head))) (head-y (head-y (current-head))) - (head-maxx (+ head-x (head-width (current-head)))) - (head-maxy (+ head-y (head-height (current-head))))) + (head-width (head-width (current-head))) + (head-height (head-height (current-head))) + (margin-x (if (> (* *message-window-margin* 2) head-width) 0 *message-window-margin*)) + (margin-y (if (> (* *message-window-y-margin* 2) head-height) 0 *message-window-y-margin*)) + (minx (+ head-x margin-x)) + (miny (+ head-y margin-y)) + (maxx (- (+ head-x head-width) margin-x)) + (maxy (- (+ head-y head-height) margin-y))) (multiple-value-bind (x y) - (gravity-coords gravity w h head-x head-y head-maxx head-maxy) + (gravity-coords gravity w h minx miny maxx maxy) (setf (xlib:drawable-y win) (max head-y y) (xlib:drawable-x win) (max head-x x)))))) diff --git a/minor-modes.lisp b/minor-modes.lisp index 16979991..e9d720e1 100644 --- a/minor-modes.lisp +++ b/minor-modes.lisp @@ -763,10 +763,13 @@ DESIGNATOR in the minor mode scope hash table." (defun scope-current-object-function (designator) (cadr (get-scope designator))) (defun scope-all-objects-function (designator) - (let ((type (first (get-scope designator)))) + (let* ((scope (get-scope designator)) + (type (first scope)) + (filter (third scope))) (lambda () (loop for object in (list-mode-objects nil) - when (typep object type) + when (and (typep object type) + (typep object filter)) collect object)))) (defun find-active-global-minor-modes-for-scope (scope) (loop for mode in *active-global-minor-modes* @@ -880,12 +883,9 @@ provided." (define-minor-mode-scope (:dynamic-group dynamic-group) (current-group)) -(defun %manual-tiling-group-p (g) - (and (typep g 'tile-group) - (not (typep g 'dynamic-group)))) - -(define-minor-mode-scope (:manual-tiling-group tile-group - (satisfies %manual-tiling-group-p)) +(define-minor-mode-scope (:manual-tiling-group + tile-group + (and tile-group (not dynamic-group))) (current-group)) (define-minor-mode-scope (:frame frame) @@ -896,12 +896,8 @@ provided." (define-minor-mode-scope (:head head) (current-head)) -(defun %frame-but-not-head (o) - (and (typep o 'frame) - (not (typep o 'head)))) - (define-descended-minor-mode-scope :frame-excluding-head :frame - :filter-type (satisfies %frame-but-not-head)) + :filter-type (and frame (not head))) (define-minor-mode-scope (:window window) (current-window)) diff --git a/primitives.lisp b/primitives.lisp index a498ec5e..65f220d0 100644 --- a/primitives.lisp +++ b/primitives.lisp @@ -75,6 +75,8 @@ *window-parent-events* *message-window-padding* *message-window-y-padding* + *message-window-margin* + *message-window-y-margin* *message-window-gravity* *message-window-real-gravity* *message-window-input-gravity* @@ -245,16 +247,15 @@ appear for. This must be an integer. If falsy, default to *timeout-wait*.") "The background color of the grabbed pointer.") ;;; Hooks - (defvar *command-mode-start-hook* '(command-mode-start-message) - "A hook called whenever command mode is started") + "A hook called whenever command mode is started.") (defvar *command-mode-end-hook* '(command-mode-end-message) - "A hook called whenever command mode is ended") + "A hook called whenever command mode is ended.") (defvar *urgent-window-hook* '() "A hook called whenever a window sets the property indicating that - it demands the user's attention") + it demands the user's attention. Called with the window as an argument.") (defvar *map-window-hook* '() "A hook called whenever a window is mapped.") @@ -265,10 +266,11 @@ appear for. This must be an integer. If falsy, default to *timeout-wait*.") (defvar *new-window-hook* '() "A hook called whenever a window is added to the window list. This includes a genuinely new window as well as bringing a withdrawn window -back into the window list.") +back into the window list. Called with the window as an argument.") (defvar *destroy-window-hook* '() - "A hook called whenever a window is destroyed or withdrawn.") + "A hook called whenever a window is destroyed or withdrawn. +Called with the window as an argument.") (defvar *focus-window-hook* '() "A hook called when a window is given focus. It is called with 2 @@ -276,7 +278,7 @@ arguments: the current window and the last window (could be nil).") (defvar *place-window-hook* '() "A hook called whenever a window is placed by rule. Arguments are -window group and frame") +window, group and frame.") (defvar *pre-thread-hook* '() "A hook called before any threads are started. Useful if you need to fork.") @@ -373,7 +375,6 @@ with 1 argument: the menu.") (defvar *new-head-hook* '() "A hook called whenever a head is added. It is called with 2 arguments: the new head and the current screen.") - ;; Data types and globals used by stumpwm (defvar *display* nil @@ -470,6 +471,16 @@ Include only those we are ready to support.") (defvar *message-window-y-padding* 0 "The number of pixels that pad the text in the message window vertically.") +(defvar *message-window-margin* 0 + "The number of pixels (i.e. the gap) between the message window and the + horizontal edges of the head. The margin is disregarded if it takes more + space than is available.") + +(defvar *message-window-y-margin* 0 + "The number of pixels (i.e. the gap) between the message window and the + vertical edges of the head. The margin is disregarded if it takes more + space than is available.") + (defvar *message-window-gravity* :top-right "This variable controls where the message window appears. The following are valid values. @@ -771,7 +782,7 @@ exist, in which case they go into the current group.") (defvar *window-number-map* "0123456789" "Set this to a string to remap the window numbers to something more convenient.") -(defvar *group-number-map* "1234567890" +(defvar *group-number-map* "123456789" "Set this to a string to remap the group numbers to something more convenient.") (defvar *frame-number-map* "0123456789abcdefghijklmnopqrstuvwxyz" @@ -1323,7 +1334,8 @@ add rules") (defmacro define-frame-preference (target-group &body frame-rules) "Create a rule that matches windows and automatically places them in -a specified group and frame. Each frame rule is a lambda list: +a specified group and frame or converts them to floating windows. Each +frame rule is a lambda list: @example \(frame-number raise lock &key from-group create restore dump-name class class-not instance instance-not type type-not role role-not title title-not @@ -1336,7 +1348,9 @@ When nil, rule applies in the current group. When non nil, @var{lock} determines applicability of rule @item frame-number -The frame number to send matching windows to +The frame number to send matching windows to. If set to :float instead of a +frame number, the window will be converted to a floating window. This is +convenient for applications that should be launched as pop-ups. @item raise When non-nil, raise and focus the window in its frame diff --git a/stumpwm-tests.asd b/stumpwm-tests.asd deleted file mode 100644 index dfaa6c9f..00000000 --- a/stumpwm-tests.asd +++ /dev/null @@ -1,12 +0,0 @@ -(defsystem "stumpwm-tests" - :name "StumpWM tests" - :serial t - :depends-on ("stumpwm" - "fiasco") - :pathname "tests/" - :components ((:file "package") - (:file "kmap") - (:file "pathnames") - (:file "mode-line-formatters")) - :perform (test-op (o c) - (uiop/package:symbol-call "FIASCO" "RUN-TESTS" 'stumpwm-tests))) diff --git a/stumpwm.asd b/stumpwm.asd index c692141c..a1658305 100644 --- a/stumpwm.asd +++ b/stumpwm.asd @@ -7,7 +7,7 @@ (defsystem :stumpwm :name "StumpWM" :author "Shawn Betts " - :version "1.0.1" + :version "23.11" :maintainer "David Bjergaard " ;; :license "GNU General Public License" :description "A tiling, keyboard driven window manager" @@ -67,7 +67,7 @@ ;; keep this last so it always gets recompiled if ;; anything changes (:file "version")) - :in-order-to ((test-op (test-op "stumpwm-tests")))) + :in-order-to ((test-op (test-op "stumpwm/tests")))) (defsystem "stumpwm/build" :depends-on ("stumpwm") @@ -76,5 +76,18 @@ :entry-point "stumpwm:main" :components ((:file "main"))) +(defsystem "stumpwm/tests" + :name "StumpWM tests" + :serial t + :depends-on ("stumpwm" + "fiasco") + :pathname "tests/" + :components ((:file "package") + (:file "kmap") + (:file "pathnames") + (:file "mode-line-formatters")) + :perform (test-op (o c) + (uiop/package:symbol-call "FIASCO" "RUN-TESTS" 'stumpwm-tests))) + ;; Quicklisp prefers systems in the central registry over its own systems (push (asdf:system-relative-pathname "stumpwm" "dynamic-mixins/") asdf:*central-registry*) diff --git a/stumpwm.texi.in b/stumpwm.texi.in index 7c9a748b..afdc7c48 100644 --- a/stumpwm.texi.in +++ b/stumpwm.texi.in @@ -162,6 +162,7 @@ Windows * Programming With Windows:: * Rule Based Window Placement:: * Window Selection Expressions:: + Frames * Interactively Resizing Frames:: @@ -1655,6 +1656,8 @@ set these color variables. ### *message-window-padding* ### *message-window-y-padding* +### *message-window-margin* +### *message-window-y-margin* ### *message-window-gravity* ### *message-window-input-gravity* ### *message-window-timer* @@ -2542,7 +2545,7 @@ minor mode scoped to @code{:GROUP} cant be a subclass of a minor mode scoped to @code{:WINDOW}, for example. However there is a way to override this by explicitly stating that two otherwise incompatible scopes are compatible. This is done by defining methods for the generic -function @code{VALIDATE-SUPERSCOPE} which dispatch upon the scope +function @code{VALIDATE-SUPERSCOPE} which specialize upon the scope designators. Such methods should return at least one value, indicating if the superscope is a valid parent of the scope. If multiple values are returned, the second value must indicate whether the superscope is an @@ -2564,6 +2567,20 @@ it is created. For this reason it is important when defining a minor mode or minor mode scope to understand the type hierarchy. It may also be in the programmers best interests to define an accompanying type. +Below is an example of a minor mode scope definition that descends from +the @code{:WINDOW} scope, and is restricted to only firefox +windows. This would be used to implement a minor mode that only gets +mixed in to firefox windows, for example to implement key rebinding. + +@verbatim +(defun %firefox-window-p (w) + (and (typep w 'window) + (string-equal (window-class window) "Firefox"))) + +(define-descended-minor-mode-scope :firefox-window :window + :filter-type (satisfies %firefox-window-p)) +@end verbatim + The following scopes are predefined: @itemize @item diff --git a/tile-group.lisp b/tile-group.lisp index ec696c70..d9b6f030 100644 --- a/tile-group.lisp +++ b/tile-group.lisp @@ -482,7 +482,7 @@ T (default) then also focus the frame." "Return a copy of the frame tree." (cond ((null tree) tree) ((typep tree 'frame) - (copy-structure tree)) + (copy-frame tree)) (t (mapcar #'copy-frame-tree tree)))) @@ -564,7 +564,7 @@ T (default) then also focus the frame." If ratio is an integer return the number of pixel desired." (if (integerp ratio) ratio - (* length ratio))) + (round (* length ratio)))) (defun funcall-on-leaf (tree leaf fn) "Return a new tree with LEAF replaced with the result of calling FN on LEAF." diff --git a/window.lisp b/window.lisp index 505326a1..eec89233 100644 --- a/window.lisp +++ b/window.lisp @@ -1253,14 +1253,18 @@ formatting. This is a simple wrapper around the command @command{windowlist}." (defcommand-alias insert window-send-string) -(defcommand mark () () -"Toggle the current window's mark." - (let ((win (current-window))) - (when win - (setf (window-marked win) (not (window-marked win))) +(defcommand mark (&optional (win (current-window)) (message t)) () +"Toggle a window's mark. The optional argument WIN controls which window is +marked and defaults to the current window. The optional argument MESSAGE +controls whether or not to display a message to the user indicating that WIN has +been marked, and defaults to T." + (when win + (setf (window-marked win) (not (window-marked win))) + (when message (message (if (window-marked win) - "Marked!" - "Unmarked!"))))) + "^3~A^n Marked!" + "^3~A^n Unmarked!") + (format-expand *window-formatters* *window-format* win))))) (defcommand clear-window-marks (&optional (group (current-group)) (windows (group-windows group))) () "Clear all marks in the current group."