diff --git a/stumpwm/Daybreak.lisp b/stumpwm/Daybreak.lisp new file mode 100644 index 0000000..75f8832 --- /dev/null +++ b/stumpwm/Daybreak.lisp @@ -0,0 +1,89 @@ +;; ----- Colors ----- +;; +;; +;; #361f24 black (background) +;; #832f01 red +;; #fb9756 orange +;; #ffde6e yellow +;; #6b789b blue +;; #7c6a8e purple +;; #94b5ea light blue +;; #fef9cd white (foreground) +;; +;; #ffffff white (comment) +;; #270e05 (current line) +;; fffffff (selection) +;; #bd4307 (orange) + +(set-fg-color "#fef9cd") +(set-bg-color "#361f24") + +;;----- Boarder ----- + +;; Set focus and unfocus colors +(stumpwm:set-focus-color "#ffffff") +(stumpwm:set-unfocus-color "#270e05") +(stumpwm:set-float-focus-color "#ffffff") +(stumpwm:set-float-unfocus-color "#270e05") + +;;--- gaps --- + +(load-module "swm-gaps") +(setf swm-gaps:*head-gaps-size* 0 + swm-gaps:*inner-gaps-size* 5 + swm-gaps:*outer-gaps-size* 40) +(when *initializing* + (swm-gaps:toggle-gaps)) + +;;--- Font --- + +(load-module "ttf-fonts") +(setq clx-truetype::*font-dirs* + (append (list (namestring "~/.local/share/fonts" + )) + clx-truetype::*font-dirs*)) +(set-font (make-instance 'xft:font + :family "DejaVuSansM Nerd Font" + :subfamily "Bold" + :size 11)) + +;;---------- Mode Line ---------- + +;; Run a shell command and format the output +(defun run-shell-command-and-format (command) + (substitute #\Space #\Newline (run-shell-command command t))) + +;; Show the kernel version +(defun show-kernel () + (run-shell-command-and-format "uname -r")) + +;; Show the hostname +(defun show-hostname () + (run-shell-command-and-format "hostname")) + +;; Show the window title +(defun show-window-title () + (substitute #\Space #\Newline (window-title (current-window)))) + +;; Mode Line Appearance +(setf stumpwm:*mode-line-background-color* "#270e05" + stumpwm:*mode-line-foreground-color* "#fef9cd" + stumpwm:*mode-line-border-color* "#ffffff" + stumpwm:*mode-line-border-width* 0 + stumpwm:*mode-line-pad-x* 0 + stumpwm:*mode-line-pad-y* 0 + stumpwm:*mode-line-timeout* 5) + +(when *initializing* + (update-color-map (current-screen))) + +;; Define the screen mode line format +(setf stumpwm:*screen-mode-line-format* + (list "%g : %v ^>^7 : " + '(:eval (show-hostname)) + ": " '(:eval (show-kernel)) + ": %d")) + +;; Enable mode line +(dolist (X11-Head (screen-heads (current-screen))) + (enable-mode-line (current-screen) X11-Head t)) diff --git a/stumpwm/autostart.lisp b/stumpwm/autostart.lisp new file mode 100644 index 0000000..415b8b8 --- /dev/null +++ b/stumpwm/autostart.lisp @@ -0,0 +1,2 @@ +;; Set wallpaper +(run-shell-command "hsetroot -fill ~/.local/share/wallpapers/P1020304.JPG") diff --git a/stumpwm/binds.lisp b/stumpwm/binds.lisp new file mode 100644 index 0000000..c33430f --- /dev/null +++ b/stumpwm/binds.lisp @@ -0,0 +1,22 @@ +;; Start Kitty +(stumpwm:define-key stumpwm:*root-map* (stumpwm:kbd "c") "exec kitty") +(stumpwm:define-key stumpwm:*root-map* (stumpwm:kbd "C-c") "exec kitty") + +;; Start Qutebrowser +(stumpwm:define-key stumpwm:*root-map* (stumpwm:kbd "w") "exec qutebrowser") +(stumpwm:define-key stumpwm:*root-map* (stumpwm:kbd "C-w") "exec qutebrowser") + +;; ;; Move window and focus +;; (define-interactive-keymap move-window nil +;; ((kbd "h") "move-focus left") +;; ((kbd "j") "move-focus down") +;; ((kbd "k") "move-focus up") +;; ((kbd "l") "move-focus right") +;; ((kbd "H") "move-window left") +;; ((kbd "J") "move-window down") +;; ((kbd "K") "move-window up") +;; ((kbd "L") "move-window right")) + +;; Restart StumpWM +(define-key *root-map* (kbd "O") "restart-soft") + diff --git a/stumpwm/groups.lisp b/stumpwm/groups.lisp new file mode 100644 index 0000000..28dd878 --- /dev/null +++ b/stumpwm/groups.lisp @@ -0,0 +1,8 @@ +(when *initializing* + (grename "[EMACS]") + (gnewbg "[TERM]") + (gnewbg "[WWW]") + (gnewbg "[PRIV]") + (gnewbg "[FILES]")) + +(clear-window-placement-rules) diff --git a/stumpwm/init.lisp b/stumpwm/init.lisp new file mode 100644 index 0000000..f6da8cc --- /dev/null +++ b/stumpwm/init.lisp @@ -0,0 +1,33 @@ +#-quicklisp +(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" + (user-homedir-pathname)))) + (when (probe-file quicklisp-init) + (load quicklisp-init))) + +(in-package :stumpwm) + +;; add ~/.stumpwm-contrib as effectively and additional *module-dir* +(mapcar #'add-to-load-path (build-load-path + (concat (getenv "HOME") "/.stumpwm-contrib"))) + +;;--- Log --- + +;; log everything to ~/.stumpwm.d/stumpwm.log +(redirect-all-output (merge-pathnames *data-dir* "stumpwm.log")) + +;;--------- Autostart -------- + +(load "~/.stumpwm.d/autostart.lisp") + +;;----- Binds ----- + +(load "~/.stumpwm.d/binds.lisp") + +;;----- Theme ----- + +(load "~/.stumpwm.d/Daybreak.lisp") + +;;------ Groups ------ + +(load "~/.stumpwm.d/groups.lisp") + diff --git a/stumpwm/modules/acpi/README.org b/stumpwm/modules/acpi/README.org new file mode 100644 index 0000000..06f64f2 --- /dev/null +++ b/stumpwm/modules/acpi/README.org @@ -0,0 +1,25 @@ +* ACPI + +Add basic battery information to the modeline. Depends on Linux's ~acpi~ +package. + +Use ~%B~ to add to the modeline. + +Exports the following variables: + +#+BEGIN_SRC common-lisp +(defvar *battery-refresh-time* 30 + "Time in seconds between updates of battery information.") + +(defvar *red* 25 + "Percentage at which the battery information turns red.") + +(defvar *yellow* 50 + "Percentage at which the battery information turns yellow.") + +(defvar *green* 75 + "Percentage at which the battery information turns green.") + +(defvar *disappear* 96 + "Percentage at which the battery information disappears from the modeline.") +#+END_SRC diff --git a/stumpwm/modules/acpi/acpi.asd b/stumpwm/modules/acpi/acpi.asd new file mode 100644 index 0000000..4eb7dbe --- /dev/null +++ b/stumpwm/modules/acpi/acpi.asd @@ -0,0 +1,11 @@ +;;;; acpi.asd + +(asdf:defsystem #:acpi + :description "View the output of acpi in the modeline." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm #:cl-ppcre) + :components ((:file "package") + (:file "acpi"))) diff --git a/stumpwm/modules/acpi/acpi.lisp b/stumpwm/modules/acpi/acpi.lisp new file mode 100644 index 0000000..29620f4 --- /dev/null +++ b/stumpwm/modules/acpi/acpi.lisp @@ -0,0 +1,52 @@ +;;;; acpi.lisp + +(in-package #:acpi) + +(defvar *acpi-refresh-time* 30 + "Time in seconds between updates of acpi information.") + +(defvar *red* 25 + "Percentage at which the acpi information turns red.") + +(defvar *yellow* 50 + "Percentage at which the acpi information turns yellow.") + +(defvar *green* 75 + "Percentage at which the acpi information turns green.") + +(defvar *disappear* 96 + "Percentage at which the acpi information disappears from the modeline.") + +(defun acpi () + "Return the percentages returned from the acpi command as a concatenated string" + (let* ((bat (run-shell-command "acpi" t)) + (bats (ppcre:all-matches-as-strings "[0-9]+%" bat)) + (ints (mapcar (lambda (b) (parse-integer b :junk-allowed t)) bats))) + (format nil "~{~A~}" + (mapcar (lambda (int bat) + (cond + ((and (> *disappear* int) (<= *green* int)) bat) + ((and (> *green* int) (<= *yellow* int)) (concat "^2*" bat)) + ((and (> *yellow* int) (<= *red* int)) (concat "^3*" bat)) + ((and (> *red* int)) (concat "^1*" bat)) + (t ""))) + ints bats)))) + +(defcommand acpi-message () () + (let ((bat (acpi))) + (if (string= bat "") + (message "Charged") + (message bat)))) + +;; pinched from battery portable code +(let ((next 0) + (last-value "")) + (defun get-acpi (ml) + (declare (ignore ml)) + (let ((now (get-universal-time))) + (when (< now next) + (return-from get-acpi last-value)) + (setf next (+ now *acpi-refresh-time*))) + (setf last-value (acpi)))) + +(add-screen-mode-line-formatter #\B #'get-acpi) diff --git a/stumpwm/modules/acpi/package.lisp b/stumpwm/modules/acpi/package.lisp new file mode 100644 index 0000000..f6c798e --- /dev/null +++ b/stumpwm/modules/acpi/package.lisp @@ -0,0 +1,9 @@ +;;;; package.lisp + +(defpackage #:acpi + (:use #:cl #:stumpwm #:cl-ppcre) + (:export #:*acpi-refresh-time* + #:*red* + #:*yellow* + #:*green* + #:*disappear*)) diff --git a/stumpwm/modules/bind/README.org b/stumpwm/modules/bind/README.org new file mode 100644 index 0000000..95009bb --- /dev/null +++ b/stumpwm/modules/bind/README.org @@ -0,0 +1,50 @@ +* Bind Key + +Slightly less clunky that the standard method for key binding. Influenced by +Emacs' [[https://github.com/jwiegley/use-package/blob/master/bind-key.el][bind-key]], but not nearly as powerful or feature rich. This is just a +couple of pretty basic macros.. + +** Usage + +#+begin_src common-lisp + (load-module "bind") +#+end_src + +The syntax is as follows: + +#+begin_src common-lisp + (bind:key + ,*key-map* + ("C-M-z" "some-amazing-stumpwm-command") + ("C-M-y" "some-other-amazing-stumpwm-command")) +#+end_src + +** Examples + +To bind Emacs to = + e= & XTerm to = + t= in the =*top-map*=, +would look like this: + +#+begin_src common-lisp + (bind:key + ,*top-map* + ("s-e" "exec emacs") + ("s-t" "exec xterm")) +#+end_src + +To do something similar in the =*root-map*=: + +#+begin_src common-lisp + (bind:key + ,*root-map* + ("e" "exec emacs") + ("t" "exec xterm")) +#+end_src + +== and == to go down and up menus. + +#+begin_src common-lisp + (bind:key + ,*menu-map* + ("TAB" 'menu-down) + ("ISO_Left_Tab" 'menu-up)) +#+end_src diff --git a/stumpwm/modules/bind/bind.asd b/stumpwm/modules/bind/bind.asd new file mode 100644 index 0000000..0a665da --- /dev/null +++ b/stumpwm/modules/bind/bind.asd @@ -0,0 +1,11 @@ +;;;; bind.asd + +(asdf:defsystem #:bind + :description "Simpler keybinding syntax" + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "bind"))) diff --git a/stumpwm/modules/bind/bind.lisp b/stumpwm/modules/bind/bind.lisp new file mode 100644 index 0000000..f2b2c5c --- /dev/null +++ b/stumpwm/modules/bind/bind.lisp @@ -0,0 +1,10 @@ +;;;; bind.lisp + +(in-package #:bind) + +(defmacro defkey (map key cmd) + `(define-key ,map (kbd ,key) ,cmd)) + +(defmacro key (map &rest keys) + (let ((ks (mapcar #'(lambda (k) (cons 'defkey (cons map k))) keys))) + `(progn ,@ks))) diff --git a/stumpwm/modules/bind/package.lisp b/stumpwm/modules/bind/package.lisp new file mode 100644 index 0000000..e7ae853 --- /dev/null +++ b/stumpwm/modules/bind/package.lisp @@ -0,0 +1,11 @@ +;;;; package.lisp + +(defpackage #:bind + (:use #:cl #:stumpwm) + (:import-from #:stumpwm + *exchange-window-map* + *groups-map* + *menu-map* + *root-map* + *top-map*) + (:export #:key)) diff --git a/stumpwm/modules/brightness/README.org b/stumpwm/modules/brightness/README.org new file mode 100644 index 0000000..c0dbc8c --- /dev/null +++ b/stumpwm/modules/brightness/README.org @@ -0,0 +1,4 @@ +* BRIGHTNESS CONTROL + +This module depends on the brightnessctl Linux command line utility for +changing monitor backlighting. diff --git a/stumpwm/modules/brightness/brightness.asd b/stumpwm/modules/brightness/brightness.asd new file mode 100644 index 0000000..1651c3f --- /dev/null +++ b/stumpwm/modules/brightness/brightness.asd @@ -0,0 +1,11 @@ +;;;; brightness.asd + +(asdf:defsystem #:brightness + :description "Use brightnessctl from StumpWM" + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "brightness"))) diff --git a/stumpwm/modules/brightness/brightness.lisp b/stumpwm/modules/brightness/brightness.lisp new file mode 100644 index 0000000..139ae67 --- /dev/null +++ b/stumpwm/modules/brightness/brightness.lisp @@ -0,0 +1,16 @@ +;;;; brightness.lisp + +(in-package #:brightness) + +(defun get-brightness (action) + "Depends on the brightnessctl command line utility." + (let* ((output (run-shell-command (concat "brightnessctl s " action) t)) + (start (search "(" output)) + (end (search ")" output)) + (out (subseq output (+ start 1) end))) + out)) + +(defcommand brightness (action) ((:string "Enter Action: ")) + "Wrap brightnessctl and print a message containing current brightness." + (let ((brightness (get-brightness action))) + (message "^B^5BRIGHTNESS: ^n~a~%" brightness))) diff --git a/stumpwm/modules/brightness/package.lisp b/stumpwm/modules/brightness/package.lisp new file mode 100644 index 0000000..82598bc --- /dev/null +++ b/stumpwm/modules/brightness/package.lisp @@ -0,0 +1,4 @@ +;;;; package.lisp + +(defpackage #:brightness + (:use #:cl #:stumpwm)) diff --git a/stumpwm/modules/cycle-mru/README.org b/stumpwm/modules/cycle-mru/README.org new file mode 100644 index 0000000..6579ace --- /dev/null +++ b/stumpwm/modules/cycle-mru/README.org @@ -0,0 +1,32 @@ +* Cycle MRU + +Cycle windows in most recently used order. + +** Description + +Flip through a group's windows, in a fashion similar to Window's Alt-Tab or +macOS' Command-Tab. A.K.A. Most Recently Used order. + +This code was lifted from [[http://nongnu.13855.n7.nabble.com/Alt-Tab-td127943.html][this thread]] on the stumpwm-devel mailing list from way +back in 2012. + +I have made some minor aesthetic and semantic changes to it (ie - changing the +name from alt-tab to cycle-mru), but full credit goes to Ruthard Baudach for the +code. + +I am a complete noob when it comes to lisp, so any improvements would be greatly +appreciated, as would any tips on how to implement the stuff on the todo list +below. + +** Usage + +#+BEGIN_SRC common-lisp +(load-module "cycle-mru") +(define-key *top-map* (kbd "M-Tab") "cycle-mru") +#+END_SRC + +** Todo + +- Use windowlist to choose, and only select on release. +- Implement reverse cycle. +- Preserve tab list during group change. diff --git a/stumpwm/modules/cycle-mru/cycle-mru.asd b/stumpwm/modules/cycle-mru/cycle-mru.asd new file mode 100644 index 0000000..916fa3d --- /dev/null +++ b/stumpwm/modules/cycle-mru/cycle-mru.asd @@ -0,0 +1,11 @@ +;;;; cycle-mru.asd + +(asdf:defsystem #:cycle-mru + :description "Cycle windows in most recently used order." + :author "Toby Slight" + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "cycle-mru"))) diff --git a/stumpwm/modules/cycle-mru/cycle-mru.lisp b/stumpwm/modules/cycle-mru/cycle-mru.lisp new file mode 100644 index 0000000..9a7596b --- /dev/null +++ b/stumpwm/modules/cycle-mru/cycle-mru.lisp @@ -0,0 +1,53 @@ +;;;; cycle-mru.lisp + +(in-package #:cycle-mru) + +(defun get-milliseconds () () + "Get timestamp in milliseconds." + (floor + (* 1000 + (/ (get-internal-real-time) + internal-time-units-per-second)))) + +(defvar *mru-last-call* (get-milliseconds)) +(defvar *mru-list* (make-list 0)) +(defvar *mru-cycle* (make-list 0)) +(defvar *mru-timeout* 500) ; in milliseconds +(defvar *mru-index* 0) ; in milliseconds + +(defun mru-new-window (win) () + "Function called by new-window-hook. Updates mru-list." + (push (window-number win) *mru-list*)) + +(defun mru-destroy-window (win) () + "Function called by destroy-window-hook. Deletes window number from + mru-list." + (setf *mru-list* (delete (window-number win) *mru-list*))) + +(defun mru-focus-window (new-win old-win) () + "Function called by focus-window-hook. Updates mru-list." + (when new-win + (setf *mru-list* (delete (window-number new-win) *mru-list*))) + (when old-win + (push (window-number old-win) *mru-list*))) + +(setf *new-window-hook* (list 'mru-new-window)) +(setf *destroy-window-hook* (list 'mru-destroy-window)) +(setf *focus-window-hook* (list 'mru-focus-window)) + +(defcommand cycle-mru () () + "Focus next windows according to most recently used order." + (cond ((< (- (get-milliseconds) *mru-last-call*) *mru-timeout*) + ;; cycle through windows + (setf *mru-index* (mod (1+ *mru-index*) (list-length *mru-cycle*))) + (select-window-by-number (nth *mru-index* *mru-cycle*))) + ;; else start anew + ((setf *mru-index* 1) (setf *mru-cycle* (copy-list *mru-list*)) + ;; if we've focussed a frame without window, current-window will + ;; return nil => select last used + (cond ((not (current-window)) + (select-window-by-number (first *mru-cycle*))) + ((push (window-number (current-window)) *mru-cycle*) + (select-window-by-number (second *mru-cycle*)))))) + ;; update timestamp + (setf *mru-last-call* (get-milliseconds))) diff --git a/stumpwm/modules/cycle-mru/package.lisp b/stumpwm/modules/cycle-mru/package.lisp new file mode 100644 index 0000000..ce1fed1 --- /dev/null +++ b/stumpwm/modules/cycle-mru/package.lisp @@ -0,0 +1,9 @@ +;;;; package.lisp + +(defpackage #:cycle-mru + (:use #:cl #:stumpwm) + (:export #:*mru-last-call* + #:*mru-list* + #:*mru-cycle* + #:*mru-timeout* + #:*mru-index*)) diff --git a/stumpwm/modules/encouragement/README.org b/stumpwm/modules/encouragement/README.org new file mode 100644 index 0000000..8ce85a6 --- /dev/null +++ b/stumpwm/modules/encouragement/README.org @@ -0,0 +1,34 @@ +* ENCOURAGEMENT + +/Scientifically-proven optimal words of hackerish encouragement./ + +Inspired by the messages printed when starting Emacs' slime mode. + +Strings lifted from [[https://github.com/kovisoft/slimv/blob/master/slime/slime.el#L1360][here]]. + +** USAGE + +Fire this sucker up with: + +#+BEGIN_SRC common-lisp +(load-module "encouragement") +#+END_SRC + +Then, run ~stump-encouragement~ from the StumpWM prompt to get an encouraging +message. Or better yet - bind it to a key. + +#+BEGIN_SRC common-lisp +(define-key *root-map* (kbd "E") "encouragement") +#+END_SRC + +I recommend adding the snippet below to your ~.stumpwmrc~ to get encouraged +every time you start StumpWM :-) + +#+BEGIN_SRC common-lisp + (setf *startup-message* (concat + "^B^5Welcome to StumpWM~%^n" + (encouragement:random-encouragement))) +#+END_SRC + +Exposes ~*words-of-encouragement*~ as a list that you can add your own +encouraging messages to. diff --git a/stumpwm/modules/encouragement/encouragement.asd b/stumpwm/modules/encouragement/encouragement.asd new file mode 100644 index 0000000..c02942c --- /dev/null +++ b/stumpwm/modules/encouragement/encouragement.asd @@ -0,0 +1,11 @@ +;;;; encouragement.asd + +(asdf:defsystem #:encouragement + :description "Scientifically-proven optimal words of hackerish encouragement." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "encouragement"))) diff --git a/stumpwm/modules/encouragement/encouragement.lisp b/stumpwm/modules/encouragement/encouragement.lisp new file mode 100644 index 0000000..3bf7438 --- /dev/null +++ b/stumpwm/modules/encouragement/encouragement.lisp @@ -0,0 +1,27 @@ +;;;; encouragement.lisp + +(in-package #:encouragement) + +(defvar *words-of-encouragement* + (list "Let the hacking commence!" + "Hacks and glory await!" + "Hack and be merry!" + "Your hacking starts... NOW!" + "May the source be with you!" + "Take this REPL, brother, and may it serve you well." + "Lemonodor-fame is but a hack away!" + "Are we consing yet?" + (concat ", this could be the start of a beautiful program..") + "Scientifically-proven optimal words of hackerish encouragement.")) + +(defun random-elt (list) + "Return a random element from a list." + (elt list (random (length list)))) + +(defun random-encouragement () + "Return a random string of hackerish encouragement." + (random-elt *words-of-encouragement*)) + +(defcommand encouragement () () + "Print a random string of hackerish encouragement." + (message "~a" (random-encouragement))) diff --git a/stumpwm/modules/encouragement/package.lisp b/stumpwm/modules/encouragement/package.lisp new file mode 100644 index 0000000..edea96c --- /dev/null +++ b/stumpwm/modules/encouragement/package.lisp @@ -0,0 +1,7 @@ +;;;; package.lisp + +(defpackage #:encouragement + (:use #:cl #:stumpwm) + (:export #:*words-of-encouragement* + #:random-encouragement + #:encouragement)) diff --git a/stumpwm/modules/global-windows/README.org b/stumpwm/modules/global-windows/README.org new file mode 100644 index 0000000..254d224 --- /dev/null +++ b/stumpwm/modules/global-windows/README.org @@ -0,0 +1,32 @@ +* Global Windows + + Out of the box StumpWM provides tools to navigate windows only within + group bounds - "windowlist" function seems to be the main, and maybe + only useful here. + + Sometimes the user wants to manage or even simply overview *all* application + windows present in X session. + + So, this contrib extension was written with purpose of providing the + way to manage global windows list in rather straightforward manner. + +** Usage + + Add this to your =.stumpwm.d/init.lisp=: + + Load contrib module: + + #+BEGIN_SRC lisp + (load-module "global-windows") + #+END_SRC + + And then use functions for global windows navigation: + + #+BEGIN_SRC lisp + (define-key *root-map* (kbd "C-t") "global-other") + (define-key *root-map* (kbd "w") "global-windowlist") + (define-key *root-map* (kbd "C-w") "global-pull-windowlist") + #+END_SRC + + Moreover there is a couple of util functions exported for the aim of + user-defined extensions - see source code. diff --git a/stumpwm/modules/global-windows/global-windows.asd b/stumpwm/modules/global-windows/global-windows.asd new file mode 100644 index 0000000..17499f5 --- /dev/null +++ b/stumpwm/modules/global-windows/global-windows.asd @@ -0,0 +1,11 @@ +;;;; global-windows.asd + +(asdf:defsystem #:global-windows + :description "Operate on windows from all groups." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "global-windows"))) diff --git a/stumpwm/modules/global-windows/global-windows.lisp b/stumpwm/modules/global-windows/global-windows.lisp new file mode 100644 index 0000000..3e12b98 --- /dev/null +++ b/stumpwm/modules/global-windows/global-windows.lisp @@ -0,0 +1,95 @@ +;;;; global-windows.lisp + +(in-package #:global-windows) + +(defvar *global-earlier-focussed-window* 'nil) +(defvar *global-prev-focussed-window* 'nil) +(defvar *global-cur-focussed-window* 'nil) + +(defun global-windows () + "Returns a list of the names of all the windows in the current screen." + (let ((groups (sort-groups (current-screen))) + (windows nil)) + (dolist (group groups) + (dolist (window (group-windows group)) + ;; Don't include the current window in the list + (when (not (eq window (current-window))) + (push window windows)))) + windows)) + +(defun global-update-windows (currwin lastwin) + "Record last visited windows and their group." + (unless (equal (cons (current-window) (current-group)) *global-cur-focussed-window*) + (when (global-find-window + (car *global-prev-focussed-window*) (screen-groups (current-screen))) + (setf *global-earlier-focussed-window* *global-prev-focussed-window*)) + (when (global-find-window + (car *global-cur-focussed-window*) (screen-groups (current-screen))) + (setf *global-prev-focussed-window* *global-cur-focussed-window*)) + (setf *global-cur-focussed-window* (cons currwin (current-group))))) + +(defun global-find-window (window group-list) + "Check for presence of window in all groups." + (if (equal (car group-list) 'nil) + 'nil + (if (member window (group-windows (car group-list))) + window + (global-find-window window (cdr group-list))))) + +(defun goto-window (window) + "Raise the window win and select its frame. For now, it does not +select the screen." + (let* ((group (window-group window)) + (frame (window-frame window)) + (old-frame (tile-group-current-frame group))) + (frame-raise-window group frame window) + (focus-all window) + (unless (eq frame old-frame) + (show-frame-indicator group)))) + +(define-stumpwm-type :global-window-names (input prompt) + (labels + ((global-window-names () + (mapcar (lambda (window) (window-name window)) (global-windows)))) + (or (argument-pop input) + (completing-read (current-screen) prompt (global-window-names))))) + +(defmacro with-global-windowlist (name docstring &rest args) + `(defcommand ,name (&optional (fmt *window-format*)) (:rest) + ,docstring + (let ((global-windows-list (global-windows))) + (labels + ((sort-windows (windowlist) + (sort1 windowlist 'string-lessp :key 'window-name))) + (if (null global-windows-list) + (message "No other windows on screen ;)") + (let ((window (select-window-from-menu (sort-windows global-windows-list) fmt))) + (when window + (progn ,@args)))))))) + +(with-global-windowlist global-windowlist + "Like windowlist, but for all groups not just the current one." + (goto-window window)) + +(with-global-windowlist global-pull-windowlist + "Global windowlist for pulling windows to the current frame." + (when (not (equalp (window-group window) + (current-group))) + (move-window-to-group window (current-group))) + (pull-window window)) + +(defcommand global-other () () + "Switch to the last used window from any group." + (let ((switch-to-win + (or + (global-find-window + (car *global-prev-focussed-window*) (screen-groups (current-screen))) + (global-find-window + (car *global-earlier-focussed-window*) (screen-groups (current-screen)))))) + (if switch-to-win + (progn + (switch-to-group (cdr *global-prev-focussed-window*)) + (focus-window (car *global-prev-focussed-window*) t)) + (message "No window to switch to.")))) + +(add-hook *focus-window-hook* 'global-update-windows) diff --git a/stumpwm/modules/global-windows/package.lisp b/stumpwm/modules/global-windows/package.lisp new file mode 100644 index 0000000..dd60dba --- /dev/null +++ b/stumpwm/modules/global-windows/package.lisp @@ -0,0 +1,32 @@ +;;;; package.lisp + +(defpackage #:global-windows + (:use #:cl #:stumpwm) + (:import-from #:stumpwm + *focus-window-hook* + *window-format* + add-hook + completing-read + current-group + current-screen + current-window + focus-all + frame-raise-window + group-windows + move-window-to-group + pull-window + select-window-from-menu + show-frame-indicator + sort-groups + sort-windows + sort1 + switch-to-group + tile-group-current-frame + window-frame + window-group + window-name) + (:export #:global-pull-windowlist + #:global-windowlist + #:global-windows + #:goto-window + #:with-global-windowlist)) diff --git a/stumpwm/modules/misc/README.org b/stumpwm/modules/misc/README.org new file mode 100644 index 0000000..ff4a8b5 --- /dev/null +++ b/stumpwm/modules/misc/README.org @@ -0,0 +1,2 @@ +* Miscellaneous StumpWM Commands +** _Toby Slight _ diff --git a/stumpwm/modules/misc/misc.asd b/stumpwm/modules/misc/misc.asd new file mode 100644 index 0000000..96a1c33 --- /dev/null +++ b/stumpwm/modules/misc/misc.asd @@ -0,0 +1,11 @@ +;;;; misc.asd + +(asdf:defsystem #:misc + :description "Miscellaneous StumpWM commands." + :author "Toby Slight " + :license "GPlv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm #:swank) + :components ((:file "package") + (:file "misc"))) diff --git a/stumpwm/modules/misc/misc.lisp b/stumpwm/modules/misc/misc.lisp new file mode 100644 index 0000000..8de1062 --- /dev/null +++ b/stumpwm/modules/misc/misc.lisp @@ -0,0 +1,59 @@ +;;;; misc.lisp + +(in-package #:misc) + +(defcommand prompt (&optional (initial "")) (:rest) + "Prompt the user for an interactive command. The first arg is an optional +initial contents." + (let ((cmd (read-one-line (current-screen) ": " :initial-input initial))) + (when cmd + (eval-command cmd t)))) + +(defcommand reload++ () () + "Reload StumpWM Configuration." + (run-commands "reload" "loadrc" "redisplay" "refresh" "refresh-heads")) + +(defcommand swank () () + "Creates a swank server in the stumpwm lisp process." + (swank-loader:init) + (setf *top-level-error-action* :break) + (swank:create-server :port 4004 + :style swank:*communication-style* + :dont-close t) + (message (concat "^B^5Getting swanky...~%^n" (random-encouragement)))) + +(defcommand toggle-split () () + (let* ((group (current-group)) + (cur-frame (tile-group-current-frame group)) + (frames (group-frames group))) + (if (eq (length frames) 2) + (progn (if (or (neighbour :left cur-frame frames) + (neighbour :right cur-frame frames)) + (progn + (only) + (vsplit)) + (progn + (only) + (hsplit)))) + (message "Works only with 2 frames")))) + +(defcommand toggle-only () () + "Toggle only one frame & restore old frame layout." + (let ((group-file (format nil "/tmp/stumpwm-group-~a" (group-name (current-group))))) + (if (null (cdr (head-frames (current-group) (current-head)))) + (restore-from-file group-file) + (progn + (dump-group-to-file group-file) + (only))))) + +(defmacro web-jump (name prefix) + `(defcommand ,(intern name) (search) + ((:rest ,(concatenate 'string name ": "))) + (substitute #\+ #\Space search) + (run-shell-command (concatenate 'string ,prefix "\"" search "\"")))) + +(web-jump "ddg" "x-www-browser https://duckduckgo.com/?q=") +(web-jump "google" "x-www-browser http://www.google.co.uk/search?q=") +(web-jump "imdb" "x-www-browser http://www.imdb.com/find?q=") +(web-jump "wikipedia" "x-www-browser http://en.wikipedia.org/wiki/") +(web-jump "youtube" "x-www-browser http://youtube.com/results?search_query=") diff --git a/stumpwm/modules/misc/package.lisp b/stumpwm/modules/misc/package.lisp new file mode 100644 index 0000000..614f3fc --- /dev/null +++ b/stumpwm/modules/misc/package.lisp @@ -0,0 +1,12 @@ +;;;; package.lisp + +(defpackage #:misc + (:use #:cl #:stumpwm #:encouragement) + (:shadow #:swank) + (:import-from #:encouragement random-encouragement) + (:import-from #:stumpwm head-frames) + (:export #:ddg + #:google + #:imdb + #:wikipedia + #:youtube)) diff --git a/stumpwm/modules/modeline-click/README.md b/stumpwm/modules/modeline-click/README.md new file mode 100644 index 0000000..69d5fee --- /dev/null +++ b/stumpwm/modules/modeline-click/README.md @@ -0,0 +1,4 @@ +# modeline-click +### Toby Slight + +Do things when we click on the modeline. diff --git a/stumpwm/modules/modeline-click/modeline-click.asd b/stumpwm/modules/modeline-click/modeline-click.asd new file mode 100644 index 0000000..9e63211 --- /dev/null +++ b/stumpwm/modules/modeline-click/modeline-click.asd @@ -0,0 +1,11 @@ +;;;; modeline-click.asd + +(asdf:defsystem #:modeline-click + :description "Do stuff when we click on the modeline." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "modeline-click"))) diff --git a/stumpwm/modules/modeline-click/modeline-click.lisp b/stumpwm/modules/modeline-click/modeline-click.lisp new file mode 100644 index 0000000..d0e7ec7 --- /dev/null +++ b/stumpwm/modules/modeline-click/modeline-click.lisp @@ -0,0 +1,52 @@ +;;;; modeline-click.lisp + +(in-package #:modeline-click) + +(defun modeline-click (modeline button x y) + "Cycle windows by scrolling and clicking on the modeline." + (let* ((screen (parse-integer + (ppcre:scan-to-strings + "[0-9]+(?=x[0-9]+x[0-9]+)" + (write-to-string (current-screen))))) + (min 50) ;; pixels + (mid (floor screen 2)) + (max (- screen min))) + (cond + ;; left corner of screen + ((and (eq button 1) (<= x min)) + (run-commands "gprev")) + ((and (eq button 3) (<= x min)) + (run-commands "gnext")) + ((and (eq button 5) (<= x min)) + (run-commands "gnext")) + ((and (eq button 4) (<= x min)) + (run-commands "gprev")) + ;; left side of screen + ((and (eq button 1) (> x min) (< x mid)) + (run-commands "prev")) + ((and (eq button 3) (> x min) (< x mid)) + (run-commands "next")) + ((and (eq button 4) (> x min) (< x mid)) + (run-commands "prev")) + ((and (eq button 5) (> x min) (< x mid)) + (run-commands "next")) + ;; right side of screen + ((and (eq button 1) (> x mid) (< x max)) + (run-commands "next")) + ((and (eq button 3) (> x mid) (< x max)) + (run-commands "prev")) + ((and (eq button 4) (> x mid) (< x max)) + (run-commands "prev")) + ((and (eq button 5) (> x mid) (< x max)) + (run-commands "next")) + ;; right corner of screen + ((and (eq button 1) (>= x max)) + (run-commands "gnext")) + ((and (eq button 3) (<= x min)) + (run-commands "gprev")) + ((and (eq button 5) (>= x max)) + (run-commands "gnext")) + ((and (eq button 4) (>= x max)) + (run-commands "gprev"))))) + +(add-hook *mode-line-click-hook* 'modeline-click) diff --git a/stumpwm/modules/modeline-click/package.lisp b/stumpwm/modules/modeline-click/package.lisp new file mode 100644 index 0000000..5f8bc46 --- /dev/null +++ b/stumpwm/modules/modeline-click/package.lisp @@ -0,0 +1,4 @@ +;;;; package.lisp + +(defpackage #:modeline-click + (:use #:cl :stumpwm)) diff --git a/stumpwm/modules/move-group/README.md b/stumpwm/modules/move-group/README.md new file mode 100644 index 0000000..f0a6de5 --- /dev/null +++ b/stumpwm/modules/move-group/README.md @@ -0,0 +1,4 @@ +# move-group +### _Toby Slight _ + +Move groups forwards and backwards diff --git a/stumpwm/modules/move-group/move-group.asd b/stumpwm/modules/move-group/move-group.asd new file mode 100644 index 0000000..3de2e93 --- /dev/null +++ b/stumpwm/modules/move-group/move-group.asd @@ -0,0 +1,11 @@ +;;;; move-group.asd + +(asdf:defsystem #:move-group + :description "Describe move-group here" + :author "Your Name " + :license "Specify license here" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "move-group"))) diff --git a/stumpwm/modules/move-group/move-group.lisp b/stumpwm/modules/move-group/move-group.lisp new file mode 100644 index 0000000..57ad42a --- /dev/null +++ b/stumpwm/modules/move-group/move-group.lisp @@ -0,0 +1,20 @@ +;;;; move-group.lisp + +(in-package #:move-group) + +(defun swap-groups (group1 group2) + (rotatef (slot-value group1 'number) (slot-value group2 'number))) + +(defun move-group-forward (&optional (group (current-group))) + (swap-groups group (next-group group (sort-groups (current-screen))))) + +(defun move-group-backward (&optional (group (current-group))) + (swap-groups group (next-group group (reverse (sort-groups (current-screen)))))) + +(defcommand gforward () () + (move-group-forward) + (echo-groups (current-screen) *group-format*)) + +(defcommand gbackward () () + (move-group-backward) + (echo-groups (current-screen) *group-format*)) diff --git a/stumpwm/modules/move-group/package.lisp b/stumpwm/modules/move-group/package.lisp new file mode 100644 index 0000000..cdf98f1 --- /dev/null +++ b/stumpwm/modules/move-group/package.lisp @@ -0,0 +1,8 @@ +;;;; package.lisp + +(defpackage #:move-group + (:use #:cl #:stumpwm) + (:import-from #:stumpwm + echo-groups + next-group + sort-groups)) diff --git a/stumpwm/modules/quitmenu/README.org b/stumpwm/modules/quitmenu/README.org new file mode 100644 index 0000000..2de4971 --- /dev/null +++ b/stumpwm/modules/quitmenu/README.org @@ -0,0 +1,8 @@ +** Quit Menu + + This module provides commands and a menu to gracefully close all open + programs and shutdown or reboot the computer. A logout command is also + provided. + + This module requires the use of =systemd=, and requires the =polkit= package + to be installed, as well as the =wmctl= command. diff --git a/stumpwm/modules/quitmenu/package.lisp b/stumpwm/modules/quitmenu/package.lisp new file mode 100644 index 0000000..6611dd5 --- /dev/null +++ b/stumpwm/modules/quitmenu/package.lisp @@ -0,0 +1,11 @@ +;;;; package.lisp + +(defpackage #:quitmenu + (:use #:cl :stumpwm) + (:export #:re-init + #:logout + #:suspend-computer + #:restart-computer + #:shutdown-computer + #:quitmenu + #:*quitmenu-menu*)) diff --git a/stumpwm/modules/quitmenu/quitmenu.asd b/stumpwm/modules/quitmenu/quitmenu.asd new file mode 100644 index 0000000..8789b71 --- /dev/null +++ b/stumpwm/modules/quitmenu/quitmenu.asd @@ -0,0 +1,10 @@ +;;;; quitmenu.asd + +(asdf:defsystem #:quitmenu + :description "Logout menu" + :author "Stuart Dilts" + :license "GPLv3" + :depends-on (#:stumpwm) + :serial t + :components ((:file "package") + (:file "quitmenu"))) diff --git a/stumpwm/modules/quitmenu/quitmenu.lisp b/stumpwm/modules/quitmenu/quitmenu.lisp new file mode 100644 index 0000000..6d866bc --- /dev/null +++ b/stumpwm/modules/quitmenu/quitmenu.lisp @@ -0,0 +1,74 @@ +;;;; session-ending.lisp +(in-package #:quitmenu) + +(defun yes-no-diag (query-string) + "Presents a yes-no dialog to the user asking query-string. +Returns true when yes is selected" + (equal :yes (cadr (select-from-menu (current-screen) + '(("Yes" :yes) ("No" :no)) + query-string)))) + +(defun close-all-apps () + "Closes all windows managed by stumpwm gracefully" + ;; yes, this uses an external tool instead of stumpwm internals + (let ((win-index-text (run-shell-command "wmctrl -l | awk '{print $1}'" t))) + (dolist (window (cl-ppcre:split "\\\n" win-index-text)) + (run-shell-command (format nil "wmctrl -i -c ~A" window))))) + +(defcommand re-init () () + "Reload StumpWM configuration" + (let ((choice (yes-no-diag "Really re-initialise??"))) + (when choice + (echo-string (current-screen) "Re-initialising...") + (run-commands + "reload" + "loadrc" + "redisplay" + "refresh" + "refresh-heads")))) + +(defcommand logout () () + (let ((choice (yes-no-diag "Really logout?"))) + (when choice + (echo-string (current-screen) "Logging out...") + (close-all-apps) + (run-hook *quit-hook*) + (quit)))) + +(defcommand suspend-computer () () + "Suspends the computer" + (let ((choice (yes-no-diag "Really suspend?"))) + (when choice + (echo-string (current-screen) "Suspending...") + (run-shell-command "systemctl suspend")))) + +;; can't name the function "restart" +(defcommand restart-computer () () + (let ((choice (yes-no-diag "Really Restart?"))) + (when choice + (echo-string (current-screen) "Restarting...") + (close-all-apps) + (run-hook *quit-hook*) + (run-shell-command "systemctl reboot")))) + +(defcommand shutdown-computer () () + (let ((choice (yes-no-diag "Really Shutdown?"))) + (when choice + (echo-string (current-screen) "Shutting down...") + (close-all-apps) + (run-hook *quit-hook*) + (run-shell-command "systemctl poweroff")))) + +(defcommand quitmenu () () + (let ((choice (select-from-menu (current-screen) *quitmenu-menu* "Quit?"))) + (when choice + (apply (second choice) nil)))) + +(defvar *quitmenu-menu* + (list (list "Restart" #'re-init) + (list "Logout" #'logout) + (list "Suspend" #'suspend-computer) + (list "Reboot" #'restart-computer) + (list "Shutdown" #'shutdown-computer)) + "The options that are available to quit a stumpwm session. +Entries in the list has the format of (\"item in menu\" #'function-to-call)") diff --git a/stumpwm/modules/ror-collection/README.org b/stumpwm/modules/ror-collection/README.org new file mode 100644 index 0000000..c14efdf --- /dev/null +++ b/stumpwm/modules/ror-collection/README.org @@ -0,0 +1,5 @@ +* Run or Raise Collection + +** _Toby Slight _ + +Run or raise collection diff --git a/stumpwm/modules/ror-collection/package.lisp b/stumpwm/modules/ror-collection/package.lisp new file mode 100644 index 0000000..e773404 --- /dev/null +++ b/stumpwm/modules/ror-collection/package.lisp @@ -0,0 +1,4 @@ +;;;; package.lisp + +(defpackage #:ror-collection + (:use #:cl #:stumpwm)) diff --git a/stumpwm/modules/ror-collection/ror-collection.asd b/stumpwm/modules/ror-collection/ror-collection.asd new file mode 100644 index 0000000..228bb56 --- /dev/null +++ b/stumpwm/modules/ror-collection/ror-collection.asd @@ -0,0 +1,11 @@ +;;;; ror-collection.asd + +(asdf:defsystem #:ror-collection + :description "Collection of run or raise commands." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "ror-collection"))) diff --git a/stumpwm/modules/ror-collection/ror-collection.lisp b/stumpwm/modules/ror-collection/ror-collection.lisp new file mode 100644 index 0000000..08cecbd --- /dev/null +++ b/stumpwm/modules/ror-collection/ror-collection.lisp @@ -0,0 +1,41 @@ +;;;; ror-collection.lisp + +(in-package #:ror-collection) + +(defcommand ror-emacs () () + "run-or-raise emacs" + (run-or-raise "emacsclient -c -a ''" '(:class "Emacs"))) + +(defcommand ror-lilyterm-with-tmux () () + "run-or-raise lilyterm with tmux" + (run-or-raise + "lilyterm -x bash -c 'tmux -q has-session && tmux attach -d || tmux -u'" + '(:class "LilyTerm"))) + +(defcommand ror-kitty-with-tmux () () + "run-or-raise kitty with tmux" + (run-or-raise + "kitty -e bash -c 'tmux -q has-session && tmux attach -d || tmux -u'" + '(:class "kitty"))) + +(defcommand ror-urxvt-with-tmux () () + "run-or-raise urxvt with tmux" + (run-or-raise + "urxvt -e bash -c 'tmux -q has-session && tmux attach -d || tmux -u'" + '(:class "URxvt"))) + +(defcommand ror-tabbed-st () () + "run-or-raise a tabbed suckless terminal" + (run-or-raise "tabbed -c -r 2 st -w ''" '(:class "tabbed"))) + +(defcommand ror-st () () + "run-or-raise suckless terminal" + (run-or-raise "st" '(:class "st-256color"))) + +(defcommand ror-web-browser () () + "run-or-raise a web browser" + (run-or-raise "x-www-browser" '(:role "browser"))) + +(defcommand ror-file-manager () () + "run-or-raise a graphical file manager" + (run-or-raise "spacefm" '(:role "file_manager"))) diff --git a/stumpwm/modules/rotate-windows/README.org b/stumpwm/modules/rotate-windows/README.org new file mode 100644 index 0000000..34b7e0b --- /dev/null +++ b/stumpwm/modules/rotate-windows/README.org @@ -0,0 +1,2 @@ +* Rotate Windows +** _Toby Slight _ diff --git a/stumpwm/modules/rotate-windows/package.lisp b/stumpwm/modules/rotate-windows/package.lisp new file mode 100644 index 0000000..2f98bf5 --- /dev/null +++ b/stumpwm/modules/rotate-windows/package.lisp @@ -0,0 +1,8 @@ +;;;; package.lisp + +(defpackage #:rotate-windows + (:use #:cl #:stumpwm) + (:import-from #:stumpwm + frame-window + group-frames + pull-window)) diff --git a/stumpwm/modules/rotate-windows/rotate-windows.asd b/stumpwm/modules/rotate-windows/rotate-windows.asd new file mode 100644 index 0000000..f578264 --- /dev/null +++ b/stumpwm/modules/rotate-windows/rotate-windows.asd @@ -0,0 +1,11 @@ +;;;; rotate-windows.asd + +(asdf:defsystem #:rotate-windows + :description "Rotate windows in frames." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "rotate-windows"))) diff --git a/stumpwm/modules/rotate-windows/rotate-windows.lisp b/stumpwm/modules/rotate-windows/rotate-windows.lisp new file mode 100644 index 0000000..bbde0cb --- /dev/null +++ b/stumpwm/modules/rotate-windows/rotate-windows.lisp @@ -0,0 +1,15 @@ +;;;; rotate-windows.lisp + +(in-package #:rotate-windows) + +(defun shift-windows-forward (frames win) + "Rotate Windows" + (when frames + (let ((frame (car frames))) + (shift-windows-forward (cdr frames) (frame-window frame)) + (when win (pull-window win frame))))) + +(defcommand rotate-windows () () + (let* ((frames (group-frames (current-group))) + (win (frame-window (car (last frames))))) + (shift-windows-forward frames win))) diff --git a/stumpwm/modules/sensors/README.org b/stumpwm/modules/sensors/README.org new file mode 100644 index 0000000..a1f59a1 --- /dev/null +++ b/stumpwm/modules/sensors/README.org @@ -0,0 +1,66 @@ +* MODELINE SENSORS DISPLAY + +View average and maximum ~sensors~ temperatures and fan RPM in the modeline. + +** PRE-REQUISITES + +Running Linux with ~lm-sensors~ installed. + +** USAGE + +To load this module, place ~(load-module sensors)~ in your ~.stumpwmrc~. + +Sensors information is then available to display in the modeline using ~%S~ in +your modeline config. + +As an example, here is my modeline config: + +#+BEGIN_SRC common-lisp + (setf *screen-mode-line-format* + (list "^3^B%d ^2[%n] ^7%v ^n ^> %S %T")) +#+END_SRC + +The following variables are exported + +#+BEGIN_SRC common-lisp + (defvar *sensors-refresh-time* 30 + "Time in seconds between updates of sensors information.") + + (defvar *red-above-temp* 60 + "Temperature to turn red at.") + + (defvar *yellow-above-temp* 50 + "Temperature to turn yellow at.") + + (defvar *display-above-temp* 40 + "Temperature to start displaying at.") + + (defvar *red-above-rpm* 4000 + "Fan RPM to turn red at.") + + (defvar *yellow-above-rpm* 3000 + "Fan RPM to turn yellow at.") + + (defvar *display-above-rpm* 2000 + "Fan RPM to start displaying at.") + + (defvar *ignore-below* 20 + "Ignore temperatures below this temperature when calculating average.") +#+END_SRC + +** CAVEATS + +This module only displays average (and maximum for temperature) temperature and +fan RPM, not all sensors. It would be good to make it more configurable in this +respect. + +Also, I'm very new to Common Lisp, so the code is pretty hacky. I'd welcome any +advice on how to make it better. + +Also credit must go to the author of [[https://github.com/tslight/stumpwm-contrib/tree/master/modeline/battery-portable][Battery Portable]] for the ~*refresh-time*~ +logic. + +** EXTRA + +As an extra bonus this module contains the stumpwm command ~sensors~ for viewing +the same information in a message window. diff --git a/stumpwm/modules/sensors/package.lisp b/stumpwm/modules/sensors/package.lisp new file mode 100644 index 0000000..fedf03a --- /dev/null +++ b/stumpwm/modules/sensors/package.lisp @@ -0,0 +1,13 @@ +;;;; package.lisp + +(defpackage #:sensors + (:use #:cl #:stumpwm #:cl-ppcre) + (:export #:*sensors-refresh-time* + #:*red-above-temp* + #:*yellow-above-temp* + #:*display-above-temp* + #:*red-above-rpm* + #:*yellow-above-rpm* + #:*display-above-rpm* + #:*ignore-below* + #:sensors)) diff --git a/stumpwm/modules/sensors/sensors.asd b/stumpwm/modules/sensors/sensors.asd new file mode 100644 index 0000000..45e9c67 --- /dev/null +++ b/stumpwm/modules/sensors/sensors.asd @@ -0,0 +1,11 @@ +;;;; sensors.asd + +(asdf:defsystem #:sensors + :description "View CPU temperature and fan RPM in modeline" + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "sensors"))) diff --git a/stumpwm/modules/sensors/sensors.lisp b/stumpwm/modules/sensors/sensors.lisp new file mode 100644 index 0000000..63427cc --- /dev/null +++ b/stumpwm/modules/sensors/sensors.lisp @@ -0,0 +1,94 @@ +;;;; sensors.lisp + +(in-package #:sensors) + +(defvar *sensors-refresh-time* 30 + "Time in seconds between updates of sensors information.") + +(defvar *red-above-temp* 60 + "Temperature to turn red at.") + +(defvar *yellow-above-temp* 50 + "Temperature to turn yellow at.") + +(defvar *display-above-temp* 40 + "Temperature to start displaying at.") + +(defvar *red-above-rpm* 4000 + "Fan RPM to turn red at.") + +(defvar *yellow-above-rpm* 3000 + "Fan RPM to turn yellow at.") + +(defvar *display-above-rpm* 2000 + "Fan RPM to start displaying at.") + +(defvar *ignore-below* 20 + "Ignore temperatures below this temperature when calculating average.") + +(defvar *temp-regex* "(?<=\\+).*[0-9]+(?=\\..*)" + "A regex that captures all temperatures.") + +(defvar *fan-regex* "(?<=\\:).*?(?=RPM)" + "A regex that captures all fans.") + +(defun sensors-as-ints (output regex) + "Use REGEX to extract sensor values from OUTPUT, and then cast them to a list + of integers if they are greater than *IGNORE-BELOW*." + (let ((strings (ppcre:all-matches-as-strings regex output))) + (mapcan ;; https://stackoverflow.com/a/13269952 + (lambda (s) + (let ((i (parse-integer (remove-if #'alpha-char-p s) :junk-allowed t))) + ;; low readings can skew the average too much + (if (< *ignore-below* i) + (list i)))) + strings))) + +(defun fmt-sensor (value &optional + (high *red-above-temp*) + (mid *yellow-above-temp*) + (low *display-above-temp*)) + "If VALUE is greater than HIGH, MID or LOW, return it as a red, yellow or + normal coloured string, respectively. If value is lower than LOW, don't + return anything." + (cond ((< high value) (concat "^1*" (write-to-string value))) + ((< mid value) (concat "^3*" (write-to-string value))) + ((< low value) (write-to-string value)) + (t nil))) + +(defun sensors () + "Transforms the output of the sensors command into two lists of integers. One + for temperatures and one for fan speeds. Calculate the average values of the + lists (and the maximum for temperatures), then cast those values back to + strings with appropriate color formatting. Finally concatenate those strings + into one line." + (let* ((output (run-shell-command "sensors" t)) + (temps (sensors-as-ints output *temp-regex*)) + (fans (sensors-as-ints output *fan-regex*)) + (max-temp (fmt-sensor (reduce #'max temps))) + (avg-temp (fmt-sensor + (handler-case + (floor (apply #'+ temps) (length temps)) + (division-by-zero () 0)))) + (avg-rpm (fmt-sensor + (handler-case + (floor (apply #'+ fans) (length fans)) + (division-by-zero () 0)) + *red-above-rpm* *yellow-above-rpm* *display-above-rpm*))) + (concat + (if max-temp (concat max-temp (string (code-char 176)) "C^n")) + (if avg-temp (concat " " avg-temp (string (code-char 176)) "C^n")) + (if avg-rpm (concat " " avg-rpm " RPM^n"))))) + +;; pinched from battery portable code +(let ((next 0) + (last-value "")) + (defun get-sensors (ml) + (declare (ignore ml)) + (let ((now (get-universal-time))) + (when (< now next) + (return-from get-sensors last-value)) + (setf next (+ now *sensors-refresh-time*))) + (setf last-value (sensors)))) + +(add-screen-mode-line-formatter #\S #'get-sensors) diff --git a/stumpwm/modules/theo/README.org b/stumpwm/modules/theo/README.org new file mode 100644 index 0000000..1a469ce --- /dev/null +++ b/stumpwm/modules/theo/README.org @@ -0,0 +1,21 @@ +* THEO + +/If people keep adding such huge stuff, soon stumpwm will be bigger than +emacs./ + +[[https://openbsd.org][OpenBSD]]'s [[https://www.theos.com/deraadt/][Theo]] insults lifted from the [[https://cvsweb.openbsd.org/src/usr.bin/mg/Attic/theo.c?rev=1.152&content-type=text/x-cvsweb-markup][mg source tree]]. + +** USAGE + +Fire this sucker up with: + +#+BEGIN_SRC common-lisp + (load-module "theo") +#+END_SRC + +Then, run ~theo~ from the StumpWM prompt to get a semi insulting message. Or +better yet - bind it to a key. + +#+BEGIN_SRC common-lisp + (define-key *root-map* (kbd "T") "theo") +#+END_SRC diff --git a/stumpwm/modules/theo/package.lisp b/stumpwm/modules/theo/package.lisp new file mode 100644 index 0000000..40cd1e8 --- /dev/null +++ b/stumpwm/modules/theo/package.lisp @@ -0,0 +1,7 @@ +;;;; package.lisp + +(defpackage #:theo + (:use #:cl #:stumpwm) + (:export #:*quotes* + #:random-quote + #:theo)) diff --git a/stumpwm/modules/theo/theo.asd b/stumpwm/modules/theo/theo.asd new file mode 100644 index 0000000..0d3fd3e --- /dev/null +++ b/stumpwm/modules/theo/theo.asd @@ -0,0 +1,11 @@ +;;;; theo.asd + +(asdf:defsystem #:theo + :description "Invoke Theo's wrath..." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "theo"))) diff --git a/stumpwm/modules/theo/theo.lisp b/stumpwm/modules/theo/theo.lisp new file mode 100644 index 0000000..0936b94 --- /dev/null +++ b/stumpwm/modules/theo/theo.lisp @@ -0,0 +1,130 @@ +;;;; theo.lisp + +(in-package #:theo) + +(defvar *quotes* + '("Write more code." + "Make more commits." + "That's because you have been slacking." + "slacker!" + "That's what happens when you're lazy." + "idler!" + "slackass!" + "lazy bum!" + "Stop slacking you lazy bum!" + "slacker slacker lazy bum bum bum slacker!" + "I could search... but I'm a lazy bum ;)" + "sshutup sshithead, ssharpsshooting susshi sshplats ssharking assholes." + "Lazy bums slacking on your asses." + "35 commits an hour? That's pathetic!" + "Fine software takes time to prepare. Give a little slack." + "I am just stating a fact" + "you bring new meaning to the terms slackass. I will have to invent a new term." + "if they cut you out, muddy their back yards" + "Make them want to start over, and play nice the next time." + "It is clear that this has not been thought through." + "avoid using abort(). it is not nice." + "That's the most ridiculous thing I've heard in the last two or three minutes!" + "I'm not just doing this for crowd response. I need to be right." + "I'd put a fan on my bomb.. And blinking lights..." + "I love to fight" + "No sane people allowed here. Go home." + "you have to stop peeing on your breakfast" + "feature requests come from idiots" + "henning and darren / sitting in a tree / t o k i n g / a joint or three" + "KICK ASS. TIME FOR A JASON LOVE IN! WE CAN ALL GET LOST IN HIS HAIR!" + "shame on you for following my rules." + "altq's parser sucks dead whale farts through the finest chemistry pipette's" + "screw this operating system shit, i just want to drive!" + "Search for fuck. Anytime you see that word, you have a paragraph to write." + "Yes, but the ports people are into S&M." + "Buttons are for idiots." + "We are not hackers. We are turd polishing craftsmen." + "who cares. style(9) can bite my ass" + "It'd be one fucking happy planet if it wasn't for what's under this fucking sticker." + "I would explain, but I am too drunk." + "you slackers don't deserve pictures yet" + "Vegetarian my ass" + "Wait a minute, that's a McNally's!" + "don't they recognize their moral responsibility to entertain me?" + "#ifdef is for emacs developers." + "Many well known people become net-kooks in their later life, because they lose touch with reality." + "You're not allowed to have an opinion." + "tweep tweep tweep" + "Quite frankly, SSE's alignment requirement is the most utterly retarded idea since eating your own shit." + "Holy verbose prom startup Batman." + "Any day now, when we sell out." + "optimism in man kind does not belong here" + "First user who tries to push this button, he pounds into the ground with a rant of death." + "we did farts. now we do sperm. we are cutting edge." + "the default configuration is a mixture of piss, puke, shit, and bloody entrails." + "Stop wasting your time reading people's licenses." + "doing it with environment variables is OH SO SYSTEM FIVE LIKE OH MY GOD PASS ME THE SPOON" + "Linux is fucking POO, not just bad, bad REALLY REALLY BAD" + "penguins are not much more than chickens that swim." + "i am a packet sniffing fool, let me wipe my face with my own poo" + "Whiners. They scale really well." + "in your world, you would have a checklist of 50 fucking workarounds just to make a coffee." + "for once, I have nothing to say." + "You have no idea how fucked we are" + "You can call it fart if you want to." + "wavelan is a battle field" + "You are in a maze of gpio pins, all alike, all undocumented, and a few are wired to bombs." + "And that is why humppa sucks... cause it has no cause." + "cache aliasing is a problem that would have stopped in 1992 if someone had killed about 5 people who worked at Sun." + "Don't spread rumours about me being gentle." + "If municipal water filtering equipment was built by the gcc developers, the western world would be dead by now." + "kettenis supported a new machine in my basement and all I got to do was fix a 1 character typo in his html page commit." + "industry told us a lesson: when you're an asshole, they mail you hardware" + "I was joking, really. I think I am funny :-)" + "the kernel is a harsh mistress" + "Have I ever been subtle? If my approach ever becomes subtle, shoot me." + "the acpi stabs you in the back. the acpi stabs you in the back. you die ..." + "My cats are more observant than you." + "our kernels have no bugs" + "style(9) has all these fascist rules, and i have a problem with some of them because i didn't come up with them" + "I'm not very reliable" + "I don't like control" + "You aren't being conservative -- you are trying to be a caveman." + "nfs loves everyone" + "basically, dung beetles fucking. that's what kerberosV + openssl is like" + "I would rather run Windows than use vi." + "if you assign that responsibility to non-hikers I will walk over and cripple you now." + "i ojbect two yoru splelng of achlhlocis." + "We have two kinds of developers - those that deal with their own shit and those that deal with other people's shit." + "If people keep adding such huge stuff, soon mg will be bigger than emacs." + "this change comes down to: This year, next year, 5 years from now, 10 years from now, or Oh fuck." + "backwards compatibility is king, and will remain king, until 2038." + "I don't know if the Internet's safe yet." + "Those who don't understand Unix are condemned to reinvent Multics in a browser" + "Don't tell anybody I said that." + "Complaint forms are handled in another department." + "You'd be safer using Windows than the code which was just deleted." + "Shit should not be shared." + "the randomization in this entire codebase is a grand experiment in stupid" + "My mailbox is full of shock." + "my integer overflow spidey senses are tingling." + "I'm just trying to improve the code..." + "It's a pleasure to work on code you can't make worse." + "It's largely bad style to do (int)sizeof" + "When I see Makefile.in, I know that \"in\" is short for \"insane\"." + "This is the beer. And that's why we need a hackathon." + "Kill the past with fire, and declare Duran Duran is less cool today. Await remixes of the same thing performed by new talent." + "Where did my \"fuck backwards compat\" compatriots go?" + "I want a new vax, one that's not so slow." + "This sausage is made from unsound meat." + "The people who wrote this code are not on your side." + "Well finally everyone can see that the shit is really shitty." + "All that complexity stopped us from getting flying cars by today.")) + +(defun random-elt (list) + "Return a random element from a list." + (elt list (random (length list)))) + +(defun random-quote () + "Return a random Theo insult." + (random-elt *quotes*)) + +(defcommand theo () () + "Print a random Theo insult." + (message "~a" (random-theo-quote))) diff --git a/stumpwm/modules/volume/README.org b/stumpwm/modules/volume/README.org new file mode 100644 index 0000000..77164c1 --- /dev/null +++ b/stumpwm/modules/volume/README.org @@ -0,0 +1,15 @@ +* VOLUME + +Display volume changes in a stumpwm message. + +#+BEGIN_SRC common-lisp + (load-module "volume") +#+END_SRC + +My setup: + +#+BEGIN_SRC common-lisp + (define-key *top-map* (kbd "XF86AudioLowerVolume") "volume Master 5%-") + (define-key *top-map* (kbd "XF86AudioRaiseVolume") "volume Master 5%+") + (define-key *top-map* (kbd "XF86AudioMute") "volume Master toggle") +#+END_SRC diff --git a/stumpwm/modules/volume/package.lisp b/stumpwm/modules/volume/package.lisp new file mode 100644 index 0000000..61e0670 --- /dev/null +++ b/stumpwm/modules/volume/package.lisp @@ -0,0 +1,5 @@ +;;;; package.lisp + +(defpackage #:volume + (:use #:cl #:stumpwm) + (:export #:volume)) diff --git a/stumpwm/modules/volume/volume.asd b/stumpwm/modules/volume/volume.asd new file mode 100644 index 0000000..a9f3ff0 --- /dev/null +++ b/stumpwm/modules/volume/volume.asd @@ -0,0 +1,11 @@ +;;;; volume.asd + +(asdf:defsystem #:volume + :description "Display volume changes as a message." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm) + :components ((:file "package") + (:file "volume"))) diff --git a/stumpwm/modules/volume/volume.lisp b/stumpwm/modules/volume/volume.lisp new file mode 100644 index 0000000..a947ade --- /dev/null +++ b/stumpwm/modules/volume/volume.lisp @@ -0,0 +1,21 @@ +;;;; volume.lisp + +(in-package #:volume) + +(defun get-volume (device action) + "Return a cleaned up version of the output of the amixer command with a given + ACTION. Simply return the volume percentage and the status of the device." + (let* ((output (run-shell-command (concat "amixer set " device " " action) t)) + (start (search "[" output)) + (end (search (concat "]" (string #\linefeed)) output)) + (out (subseq output start end))) + (split-string out "] ["))) + +(defcommand volume (device action) + ((:string "Enter Device: ") + (:string "Enter Action: ")) + "Wrap volume commands and print a message containing current volume." + (let* ((output (get-volume device action)) + (volume (nth 0 output)) + (status (string-upcase (nth 1 output)))) + (message "^B^5VOLUME: ^n~a~%^5STATUS: ^n~a" volume status))) diff --git a/stumpwm/modules/xdgmenu/README.org b/stumpwm/modules/xdgmenu/README.org new file mode 100644 index 0000000..f5bcedc --- /dev/null +++ b/stumpwm/modules/xdgmenu/README.org @@ -0,0 +1,22 @@ +* XDGMENU + +Generate a menu using [[https://freedesktop.org/wiki/Howto_desktop_files/][freedesktop.org]] .desktop files. + +** USAGE + +If run with an argument, the menu will be categorised, otherwise just a +searchable list of all desktop files in XDG applications directories will be +displayed. + +#+BEGIN_SRC common-lisp + (load-module "xdgmenu") +#+END_SRC + +My configuration: + +#+BEGIN_SRC common-lisp + (define-key *top-map* (kbd "s-SPC") "xdgmenu") + (define-key *top-map* (kbd "s-.") "xdgmenu categories") + (define-key *root-map* (kbd "r") "xdgmenu") + (define-key *root-map* (kbd ".") "xdgmenu categories") +#+END_SRC diff --git a/stumpwm/modules/xdgmenu/package.lisp b/stumpwm/modules/xdgmenu/package.lisp new file mode 100644 index 0000000..549ad94 --- /dev/null +++ b/stumpwm/modules/xdgmenu/package.lisp @@ -0,0 +1,7 @@ +;;;; package.lisp + +(defpackage #:xdgmenu + (:use #:cl #:stumpwm #:cl-ppcre) + (:export #:*main-categories* + #:*applications-directories* + #:xdgmenu)) diff --git a/stumpwm/modules/xdgmenu/xdgmenu.asd b/stumpwm/modules/xdgmenu/xdgmenu.asd new file mode 100644 index 0000000..6c299b0 --- /dev/null +++ b/stumpwm/modules/xdgmenu/xdgmenu.asd @@ -0,0 +1,11 @@ +;;;; xdgmenu.asd + +(asdf:defsystem #:xdgmenu + :description "Generate menus using freedesktop.org .desktop files." + :author "Toby Slight " + :license "GPLv3" + :version "0.0.1" + :serial t + :depends-on (#:stumpwm #:cl-ppcre) + :components ((:file "package") + (:file "xdgmenu"))) diff --git a/stumpwm/modules/xdgmenu/xdgmenu.lisp b/stumpwm/modules/xdgmenu/xdgmenu.lisp new file mode 100644 index 0000000..92f7c84 --- /dev/null +++ b/stumpwm/modules/xdgmenu/xdgmenu.lisp @@ -0,0 +1,150 @@ +;;;; xdgmenu.lisp + +(in-package #:xdgmenu) + +(defvar *applications-directories* + (list (pathname (concat + (getenv "HOME") + "/.local/share/flatpak/exports/share/applications/")) + (pathname (concat (getenv "HOME") "/.local/share/applications/")) + #P"/var/lib/flatpak/exports/share/applications/" + #P"/var/lib/snapd/desktop/applications/" + #P"/usr/local/share/applications/" + #P"/usr/share/applications/") + "uiop:xdg-data-pathnames or getenv doesn't always seem to catch all of my + environment's XDG data directories. Add them manually instead.") + +(defvar *main-categories* + (list "AudioVideo" + "Audio" + "Video" + "Development" + "Education" + "Game" + "Graphics" + "Multimedia" + "Network" + "Office" + "Other" + "Science" + "Settings" + "System" + "Utility") + "Use https://standards.freedesktop.org/menu-spec/latest/apa.html as base, but + add Multimedia and other, as I don't want 3 categories for media, and I want + to catch any applications that don't have a category with other.") + +(defun desktop-files () + "Get all the dot desktop files in all XDG applications directories." + (mapcan (lambda (d) (directory (uiop:merge-pathnames* "*.desktop" d))) + *applications-directories*)) + +(defun remove-keys (exec file) + "Some KDE apps have this -caption %c in a few variations. %c is The + translated name of the application as listed in the appropriate Name key in + the desktop entry. + + %k is the location of the desktop file as either a URI (if for example + gotten from the vfolder system) or a local filename or empty if no location + is known. + + Remove any remaining keys and trailing options from the command." + (let* ((c (ppcre:regex-replace-all " -caption.*\%c(\'|\"|)" exec "")) + (k (ppcre:regex-replace-all "(\"|\'|)\%k(\"|\'|)" c file))) + (first (split-string k "%")))) + +(defun terminalp (cmd contents) + "If the Terminal attribute is set to true append a terminal emulater to the + command." + (let ((is-terminal (string-downcase + (ppcre:scan-to-strings "(?<=\\nTerminal\\=).*" contents)))) + (if (string= is-terminal "true") + (concat "x-terminal-emulator -e " cmd) + cmd))) + +(defun get-category (contents) + "Get the first value from the Categories field that matches one of the main + freedesktop.org categories." + (let ((category (first (intersection + *main-categories* + (split-string + (ppcre:scan-to-strings + "(?<=\\Categories\\=).*" + contents) + ";") + :test 'equalp)))) + (cond ((ppcre:scan "Audio|Video" category) "Multimedia") + (nil "Other") + (t category)))) + +(defun do-show (contents) + "Return true so long as the NoDisplay or Hidden field is not set to true or + the OnlyShowIn field doesn't contain anything. If OnlyShowIn has a value, + it's likely this is a desktop specific application." + (let* ((nodisplay (string-downcase + (ppcre:scan-to-strings "(?<=\\nNoDisplay\\=).*" contents))) + (hidden (string-downcase + (ppcre:scan-to-strings "(?<=\\nHidden\\=).*" contents))) + (only (ppcre:scan-to-strings "(?<=\\nOnlyShowIn\\=).*" contents))) + (if (not (or (string= hidden "true") + (string= nodisplay "true") + (> (length only) 0))) + t))) + +(defun parse-desktop-file (file) + (let ((contents (uiop:read-file-string (pathname file)))) + (if (do-show contents) + (let* ((name (ppcre:scan-to-strings "(?<=\\nName\\=).*" contents)) + (exec (ppcre:scan-to-strings "(?<=\\nExec\\=).*" contents)) + (category (get-category contents)) + (cmd (remove-keys exec file)) + (cmd (terminalp cmd contents))) + (list name cmd category))))) + +(defun make-categories (applications) + (mapcar + (lambda (c) + (cons c + (remove nil + (mapcar + (lambda (a) + (if (equal c (third a)) + a)) + applications)))) + *main-categories*)) + +(defun menu (&optional (categorise nil)) + (let ((apps (sort (remove nil (mapcar #'parse-desktop-file (desktop-files))) + #'string-lessp :key #'first))) + (if categorise + (remove-if (lambda (c) (<= (length c) 1)) (make-categories apps)) + apps))) + +(defun commandp (command-name) + (loop + :for command :being :the :hash-keys :of *command-hash* + :when (string= (symbol-name command-name) + (symbol-name command)) + :return command)) + +(defun category-menu () + (labels + ((pick (options) + (let ((selection (select-from-menu (current-screen) options nil))) + (cond + ((null selection) nil) + ((stringp (second selection)) + (run-shell-command (second selection))) + ((and (symbolp (second selection)) + (commandp (second selection))) + (funcall (second selection))) + (t (if (equalp ".." (first selection)) + (pick (second selection)) + (pick (append (list (list ".." options)) + (cdr selection))))))))) + (pick (menu t)))) + +(defcommand xdgmenu (&optional (categorise nil)) ((:string)) + (if categorise + (category-menu) + (run-shell-command (second (select-from-menu (current-screen) (menu))))))