Spatial organization of windows in StumpWM

5 views
Skip to first unread message

Russell Adams

unread,
Mar 7, 2022, 2:12:13 PM3/7/22
to stumpw...@nongnu.org
I composed a short video about using spatial groups in StumpWM to
share. I've tried to explain the idea of spatial grouping before on
IRC, and I felt a demonstration would be clearer.

http://demosthenes.org/tmp/StumpWMSpatialGroups.mp4

Please pardon the rough edges. I'm not starting a Youtube channel,
just trying to have a conversation about how to manage windows and
groups in Stump.

Spatial grouping abuses Stump's ability to dynamically create groups
to create a sparse 3d array of groups, visualized as multiple desktops
of a 2d grid of screens. Hotkeys use arrows to navigate the grid and
swap desktops in a way which emulates a larger physical screen
surface.

Using this method allows me to group windows by project, adjacent to
each other. This allows absolute navigation to each window, as opposed
to relative navigation (ie: alt-tab).

I'm not grouping related application windows on the same screen, but
rather organizing patterns of groups to keep related application
windows cognitively near each other. I still may use splits and tile
windows within a screen, but not as often.

I'm hoping to inspire further discussion on the matter. Critically, I
have not tested multiple monitor support.

Concepts:
Spatial groups use a 3d coordinate system: “x,y,z”
Valid groups are “0,0,0”, “-1,2,0”, etc.
Z is used as the desktop index

Keys:
Control-Shift-{Left,Right}: Change desktop (Z axis)
Control-Arrows: Navigate virtual screens on current desktop
Control-Shift-Up: Return to 0,0 on current desktop (Z)
Shift-Arrows: Navigate splits on current screen group

Relevant code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(ql:quickload 'cl-ppcre)

(in-package :stumpwm)

;; change the prefix key to something else
(set-prefix-key (kbd "s-."))

;; fix scroll wheel?
(setf (getenv "GDK_CORE_DEVICE_EVENTS") "1")

;; early define global vars
(defparameter *z-cursors* '()
"Association list of groups by Z coordinate for preserving last position on that plane.")

(defparameter *last-cursor* NIL
"Previous group for popping back.")

;; Change focus
(set-focus-color "darkblue") ;; Set the border around the current frame
(setf *suppress-frame-indicator* t) ;; Stop the popup on the current frame

;; Modeline settings
(enable-mode-line (current-screen) (current-head) t)
(setf *mode-line-timeout* 60)
(setf *screen-mode-line-format* (list "[^B%n^b] %d |" "| %W"))

;; Shift arrows between adjacent windows
(define-key *top-map* (kbd "S-Up") "move-focus up")
(define-key *top-map* (kbd "S-Down") "move-focus down")
(define-key *top-map* (kbd "S-Left") "move-focus left")
(define-key *top-map* (kbd "S-Right") "move-focus right")

;; Control arrows between groups
(defcommand coord-left () () (coord-group-change -1 0 0))
(defcommand coord-right () () (coord-group-change 1 0 0))
(defcommand coord-up () () (coord-group-change 0 1 0))
(defcommand coord-down () () (coord-group-change 0 -1 0))
(defcommand coord-taskleft () () (coord-group-change 0 0 -1))
(defcommand coord-taskright () () (coord-group-change 0 0 1))

;; return to 0,0 on the current taskplane as a shortcut to return to the core task
(defcommand coord-taskorigin () ()
(gselect
(group-name
(my-find-group (current-screen)
(format nil "~{~a~^,~}" (list 0 0 (parse-integer (third (cl-ppcre:split "," (group-name (current-group)))))))))))

;; pop back to last location
(defcommand coord-taskpop () () (when *last-cursor* (my-gselect *last-cursor*)))

(define-key *top-map* (kbd "C-Left") "coord-left")
(define-key *top-map* (kbd "C-Right") "coord-right")
(define-key *top-map* (kbd "C-Up") "coord-up")
(define-key *top-map* (kbd "C-Down") "coord-down")
(define-key *top-map* (kbd "C-S-Left") "coord-taskleft")
(define-key *top-map* (kbd "C-S-Right") "coord-taskright")
(define-key *top-map* (kbd "C-S-Up") "coord-taskorigin")
(define-key *top-map* (kbd "C-S-Down") "coord-taskpop")

(define-key *top-map* (kbd "s-S-SPC") "fullscreen")


;; Groups will manage the coordinate system
;; format: 0,0,0 with positive and negative numbers
;; create groups on the fly as needed
;; only supports one screen atm, would like multimonitor support later

(gnew "0,0,0") ; create origin on startup

(defun my-find-group (screen name)
(find name (screen-groups screen) :key 'group-name :test 'string=))

(defun my-gselect (name)
"Preserve prior location for pop, and handle when group is new"
(banish)
(setf *last-cursor* (group-name (current-group)))
(gselect (group-name
(or (my-find-group (current-screen) name)
(gnew name)))))

(defun coord-group-change (xo yo zo)
"Navigate a 3d array of groups using x,y,z coordinates by passing the offset of the change."
(let* ((current-coords
(mapcar #'parse-integer
(cl-ppcre:split "," (group-name (current-group)))))
(new-coords (mapcar #'+ current-coords (list xo yo zo)))
(new-group-name (format nil "~{~a~^,~}" new-coords)) )

(if (= 0 zo)

;; Not changing taskplanes, so just move by coordinates
(my-gselect new-group-name)

;; Changing Z across taskplanes, RESTORE z-cursor for that plane
(let ((old-z (third current-coords))
(new-z (third new-coords)))

(if (assoc old-z *z-cursors*)
(setf (cdr (assoc old-z *z-cursors*)) (current-group))
(push (cons old-z (current-group)) *z-cursors*))

(let ((z-cursor (cdr (or (assoc new-z *z-cursors*) '(nil . nil)))))
(if z-cursor
;; Restore saved location
(my-gselect (group-name z-cursor))
;; create new taskplane
(my-gselect new-group-name)))))))

(defun my-startup () (my-gselect "0,0,0"))

(when *initializing* (my-startup))




------------------------------------------------------------------
Russell Adams RLA...@AdamsInfoServ.com

PGP Key ID: 0x1160DCB3 http://www.adamsinfoserv.com/

Fingerprint: 1723 D8CA 4280 1EC9 557F 66E8 1154 E018 1160 DCB3

Shostek

unread,
Mar 7, 2022, 4:01:08 PM3/7/22
to Russell Adams, stumpw...@nongnu.org
This is phenomenal! I didn't grok it when you explained on irc but the video demo made perfect sense. I think this would make an very powerful contribution to StumpWM, if not in the core than as a module. The video is also really well put together, and demonstrates the true power of stump. If you feel like it, I think it would be quite publishable. If you'd like feedback or constructive criticism I would be happy to give it when I have some time.

Now please excuse me while I add all of this code to my stumpwmrc while waiting for it to be formally published :).

7. mar. 2022 20:12:03 Russell Adams <RLA...@adamsinfoserv.com>:

Russell Adams

unread,
Mar 7, 2022, 4:53:50 PM3/7/22
to stumpw...@nongnu.org
On Mon, Mar 07, 2022 at 09:00:37PM +0000, Shostek wrote:
> This is phenomenal! I didn't grok it when you explained on irc but
> the video demo made perfect sense.

I felt like I couldn't explain it adequately.

> I think this would make an very powerful contribution to StumpWM, if
> not in the core than as a module.

Cool. Glad it's useful.

> The video is also really well put together, and demonstrates the
> true power of stump. If you feel like it, I think it would be quite
> publishable. If you'd like feedback or constructive criticism I
> would be happy to give it when I have some time.

It's a draft. I used a spreadsheet instead of a presentation, and I
left all the trailing noise I should have cut out.

On the other hand, as a draft I think it was good. I could certainly
use that as the basis for a more professional presentation.

> Now please excuse me while I add all of this code to my stumpwmrc
> while waiting for it to be formally published :).

Enjoy!

>
> 7. mar. 2022 20:12:03 Russell Adams <RLA...@adamsinfoserv.com>:
>
> > I composed a short video about using spatial groups in StumpWM to
> > share. I've tried to explain the idea of spatial grouping before on
> > IRC, and I felt a demonstration would be clearer.
> >
> > http://demosthenes.org/tmp/StumpWMSpatialGroups.mp4
> >


Tim Cross

unread,
Mar 7, 2022, 11:41:50 PM3/7/22
to Russell Adams, stumpw...@nongnu.org
Hi Russell,

this is very nice. I have something a little similar, but without the
ability to add 'desktops' dynamically. The idea to think of it as a 3d
coordinate system is excellent and I intend to steal your code!

I agree with you that once you have this working you don't tend to split
windows, but instead move between them within a desktop. Splitting
windows has never worked well for me as I have very poor sight and need
a much larger font than most. Splitting always ended up with too little
displayed (either too much wrapping/truncating or too few lines). Having
virtual desktop where you can move around windows is fast and convenient
and having each desktop remember the window layout makes it easy to move
between projects, but with a standard/common window layout.

It never ceases to amaze me how just having one inspiring idea can
create such an elegant solution. In this case, the use of the 3d
coordinates is brilliant. Now that you have shown it, it seems like such
an obvious solution, yet it simply didn't occur to me! I had thought
about how I could make my own setup support dynamic creation of
desktops, but it always seemed to be more clunky than I liked.

Now I have the key and am off to hack my config....

thanks,

Tim

Roland Everaert

unread,
Mar 8, 2022, 4:53:59 AM3/8/22
to Russell Adams, stumpw...@nongnu.org
Hello,

For some reason, I cannot play the video. I have tried from Firefox and with the mpv CLI.

The video shows up, but it doesn't play :/

Roland Everaert
---
Use the F.O.S.S., Luke

Envoyé avec la messagerie sécurisée ProtonMail.

------- Original Message -------
> (defparameter z-cursors '()
>
> "Association list of groups by Z coordinate for preserving last position on that plane.")
>
> (defparameter last-cursor NIL
>
> "Previous group for popping back.")
>
> ;; Change focus
>
> (set-focus-color "darkblue") ;; Set the border around the current frame
>
> (setf suppress-frame-indicator t) ;; Stop the popup on the current frame
>
> ;; Modeline settings
>
> (enable-mode-line (current-screen) (current-head) t)
>
> (setf mode-line-timeout 60)
>
> (setf screen-mode-line-format (list "[^B%n^b] %d |" "| %W"))
>
> ;; Shift arrows between adjacent windows
>
> (define-key top-map (kbd "S-Up") "move-focus up")
>
> (define-key top-map (kbd "S-Down") "move-focus down")
>
> (define-key top-map (kbd "S-Left") "move-focus left")
>
> (define-key top-map (kbd "S-Right") "move-focus right")
>
> ;; Control arrows between groups
>
> (defcommand coord-left () () (coord-group-change -1 0 0))
>
> (defcommand coord-right () () (coord-group-change 1 0 0))
>
> (defcommand coord-up () () (coord-group-change 0 1 0))
>
> (defcommand coord-down () () (coord-group-change 0 -1 0))
>
> (defcommand coord-taskleft () () (coord-group-change 0 0 -1))
>
> (defcommand coord-taskright () () (coord-group-change 0 0 1))
>
> ;; return to 0,0 on the current taskplane as a shortcut to return to the core task
>
> (defcommand coord-taskorigin () ()
>
> (gselect
>
> (group-name
>
> (my-find-group (current-screen)
>
> (format nil "~{~a~^,~}" (list 0 0 (parse-integer (third (cl-ppcre:split "," (group-name (current-group)))))))))))
>
> ;; pop back to last location
>
> (defcommand coord-taskpop () () (when last-cursor (my-gselect last-cursor)))
>
> (define-key top-map (kbd "C-Left") "coord-left")
>
> (define-key top-map (kbd "C-Right") "coord-right")
>
> (define-key top-map (kbd "C-Up") "coord-up")
>
> (define-key top-map (kbd "C-Down") "coord-down")
>
> (define-key top-map (kbd "C-S-Left") "coord-taskleft")
>
> (define-key top-map (kbd "C-S-Right") "coord-taskright")
>
> (define-key top-map (kbd "C-S-Up") "coord-taskorigin")
>
> (define-key top-map (kbd "C-S-Down") "coord-taskpop")
>
> (define-key top-map (kbd "s-S-SPC") "fullscreen")
>
> ;; Groups will manage the coordinate system
>
> ;; format: 0,0,0 with positive and negative numbers
>
> ;; create groups on the fly as needed
>
> ;; only supports one screen atm, would like multimonitor support later
>
> (gnew "0,0,0") ; create origin on startup
>
> (defun my-find-group (screen name)
>
> (find name (screen-groups screen) :key 'group-name :test 'string=))
>
> (defun my-gselect (name)
>
> "Preserve prior location for pop, and handle when group is new"
>
> (banish)
>
> (setf last-cursor (group-name (current-group)))
>
> (gselect (group-name
>
> (or (my-find-group (current-screen) name)
>
> (gnew name)))))
>
> (defun coord-group-change (xo yo zo)
>
> "Navigate a 3d array of groups using x,y,z coordinates by passing the offset of the change."
>
> (let* ((current-coords
>
> (mapcar #'parse-integer
>
> (cl-ppcre:split "," (group-name (current-group)))))
>
> (new-coords (mapcar #'+ current-coords (list xo yo zo)))
>
> (new-group-name (format nil "~{~a~^,~}" new-coords)) )
>
> (if (= 0 zo)
>
> ;; Not changing taskplanes, so just move by coordinates
>
> (my-gselect new-group-name)
>
> ;; Changing Z across taskplanes, RESTORE z-cursor for that plane
>
> (let ((old-z (third current-coords))
>
> (new-z (third new-coords)))
>
> (if (assoc old-z z-cursors)
>
> (setf (cdr (assoc old-z z-cursors)) (current-group))
>
> (push (cons old-z (current-group)) z-cursors))
>
> (let ((z-cursor (cdr (or (assoc new-z z-cursors) '(nil . nil)))))
>
> (if z-cursor
>
> ;; Restore saved location
>
> (my-gselect (group-name z-cursor))
>
> ;; create new taskplane
>
> (my-gselect new-group-name)))))))
>
> (defun my-startup () (my-gselect "0,0,0"))
>
> (when initializing (my-startup))

Roland Everaert

unread,
Mar 8, 2022, 5:01:20 AM3/8/22
to Russell Adams, stumpw...@nongnu.org
Nevermind, it is an issue on my side that I need to fix eventually.

Sorry for the false alarm.

Roland Everaert
---
Use the F.O.S.S., Luke

Envoyé avec la messagerie sécurisée ProtonMail.

------- Original Message -------

Roland Everaert

unread,
Mar 8, 2022, 5:52:08 AM3/8/22
to Russell Adams, stumpw...@nongnu.org
Really impressive. I hope to find some time to dig into your code and adapt my workflow and my navigation.

Thanks for the great video, I am also thinking that you should seek to publish it for the greater good.

Regards,

Roland Everaert
---
Use the F.O.S.S., Luke

Envoyé avec la messagerie sécurisée ProtonMail.

------- Original Message -------

Le lundi 7 mars 2022 à 20:11, Russell Adams <RLA...@adamsinfoserv.com> a écrit :

> (defparameter z-cursors '()
>
> "Association list of groups by Z coordinate for preserving last position on that plane.")
>
> (defparameter last-cursor NIL
>
> "Previous group for popping back.")
>
> ;; Change focus
>
> (set-focus-color "darkblue") ;; Set the border around the current frame
>
> (setf suppress-frame-indicator t) ;; Stop the popup on the current frame
>
> ;; Modeline settings
>
> (enable-mode-line (current-screen) (current-head) t)
>
> (setf mode-line-timeout 60)
>
> (setf screen-mode-line-format (list "[^B%n^b] %d |" "| %W"))
>
> ;; Shift arrows between adjacent windows
>
> (define-key top-map (kbd "S-Up") "move-focus up")
>
> (define-key top-map (kbd "S-Down") "move-focus down")
>
> (define-key top-map (kbd "S-Left") "move-focus left")
>
> (define-key top-map (kbd "S-Right") "move-focus right")
>
> ;; Control arrows between groups
>
> (defcommand coord-left () () (coord-group-change -1 0 0))
>
> (defcommand coord-right () () (coord-group-change 1 0 0))
>
> (defcommand coord-up () () (coord-group-change 0 1 0))
>
> (defcommand coord-down () () (coord-group-change 0 -1 0))
>
> (defcommand coord-taskleft () () (coord-group-change 0 0 -1))
>
> (defcommand coord-taskright () () (coord-group-change 0 0 1))
>
> ;; return to 0,0 on the current taskplane as a shortcut to return to the core task
>
> (defcommand coord-taskorigin () ()
>
> (gselect
>
> (group-name
>
> (my-find-group (current-screen)
>
> (format nil "~{~a~^,~}" (list 0 0 (parse-integer (third (cl-ppcre:split "," (group-name (current-group)))))))))))
>
> ;; pop back to last location
>
> (defcommand coord-taskpop () () (when last-cursor (my-gselect last-cursor)))
>
> (define-key top-map (kbd "C-Left") "coord-left")
>
> (define-key top-map (kbd "C-Right") "coord-right")
>
> (define-key top-map (kbd "C-Up") "coord-up")
>
> (define-key top-map (kbd "C-Down") "coord-down")
>
> (define-key top-map (kbd "C-S-Left") "coord-taskleft")
>
> (define-key top-map (kbd "C-S-Right") "coord-taskright")
>
> (define-key top-map (kbd "C-S-Up") "coord-taskorigin")
>
> (define-key top-map (kbd "C-S-Down") "coord-taskpop")
>
> (define-key top-map (kbd "s-S-SPC") "fullscreen")
>
> ;; Groups will manage the coordinate system
>
> ;; format: 0,0,0 with positive and negative numbers
>
> ;; create groups on the fly as needed
>
> ;; only supports one screen atm, would like multimonitor support later
>
> (gnew "0,0,0") ; create origin on startup
>
> (defun my-find-group (screen name)
>
> (find name (screen-groups screen) :key 'group-name :test 'string=))
>
> (defun my-gselect (name)
>
> "Preserve prior location for pop, and handle when group is new"
>
> (banish)
>
> (setf last-cursor (group-name (current-group)))
>
> (gselect (group-name
>
> (or (my-find-group (current-screen) name)
>
> (gnew name)))))
>
> (defun coord-group-change (xo yo zo)
>
> "Navigate a 3d array of groups using x,y,z coordinates by passing the offset of the change."
>
> (let* ((current-coords
>
> (mapcar #'parse-integer
>
> (cl-ppcre:split "," (group-name (current-group)))))
>
> (new-coords (mapcar #'+ current-coords (list xo yo zo)))
>
> (new-group-name (format nil "~{~a~^,~}" new-coords)) )
>
> (if (= 0 zo)
>
> ;; Not changing taskplanes, so just move by coordinates
>
> (my-gselect new-group-name)
>
> ;; Changing Z across taskplanes, RESTORE z-cursor for that plane
>
> (let ((old-z (third current-coords))
>
> (new-z (third new-coords)))
>
> (if (assoc old-z z-cursors)
>
> (setf (cdr (assoc old-z z-cursors)) (current-group))
>
> (push (cons old-z (current-group)) z-cursors))
>
> (let ((z-cursor (cdr (or (assoc new-z z-cursors) '(nil . nil)))))
>
> (if z-cursor
>
> ;; Restore saved location
>
> (my-gselect (group-name z-cursor))
>
> ;; create new taskplane
>
> (my-gselect new-group-name)))))))
>
> (defun my-startup () (my-gselect "0,0,0"))
>
> (when initializing (my-startup))

Russell Adams

unread,
Mar 8, 2022, 10:04:45 AM3/8/22
to stumpw...@nongnu.org
On Tue, Mar 08, 2022 at 10:51:28AM +0000, Roland Everaert wrote:
> Really impressive. I hope to find some time to dig into your code
> and adapt my workflow and my navigation.

Honestly it's short and needs refinement. ;]

> Thanks for the great video, I am also thinking that you should seek
> to publish it for the greater good.

I think that video qualifies as a draft. If this feature goes core,
I'll try to make a more professional video to help showcase the
feature.

Thanks.

Roland Everaert

unread,
Mar 8, 2022, 11:34:35 AM3/8/22
to Russell Adams, stumpw...@nongnu.org
As you speak about going to the core of StumpWM, I was wondering if this could be packaged as a module, but making it core would be even better, indeed.

Roland Everaert
---
Use the F.O.S.S., Luke

Envoyé avec la messagerie sécurisée ProtonMail.

------- Original Message -------

Reply all
Reply to author
Forward
0 new messages