I am an =Emacs= user and previously used =StumpWM=, an =X11= window manager written in =Common Lisp=. I believe window managers should be scriptable because the level of workflow customization required by users often exceeds what can be achieved with simple configuration parameters (see my workflow below for a clearer understanding of why this is the case). Unfortunately, =Sway/i3= lacks a straightforward programmable interface for customization. This project provides complete control over =Sway/i3= using =Guile=!
** Why Sway?
I had to migrate to =Wayland= at some point. Being a big fan of =StumpWM=, I tried to replicate a similar environment in one of the =Wayland= window managers. I made some progress with =hyprland= using a set of =Guile= bindings I developed called =hypripc=, but I found that =Hyprland= isn't as stable as =Sway=.
After cloning this repository, you can immediately test it using the provided =examples/playground/example.scm= file in the directory, which demonstrates some of the features available in this package.
You can retrieve information about =Sway=, such as list of available =workspaces= or =outputs=. The response will be in Guile records, which you can easily manipulate! (refer to =swayipc/info.scm=)
You can assign keybindings that execute Guile code! Obviously, running shell commands is straightforward since you're operating within Guile. Additionally, you have full access to Sway/i3 specific commands (refer to =swayipc/dispatcher.scm=).
Certain scenarios necessitate subscribing to events. One example from my =workflow= described below requires this capability. With =swayipc=, you have the ability to listen for events and execute actions in response.
Note: To receive any events, you must subscribe to them. You can subscribe to individual events that interest you or to all available events. Without subscribing and running the event listener in your =init.scm=, your hooks will not receive any events.
The event listener thread is a Unix socket that waits for sway events. This must be executed, preferably as the last expression in your =init.scm= file, because =thread-join= will block execution. This blocking is necessary to keep the listener active and prevent the script from exiting.
Most of the source code is documented. You can refer to =examples/stumpwm-like/init.scm= for a complex stumpwm like configuration example. Here are some important points to consider before hacking your Sway setup
1- You can start your =swayipc= configurations from the =REPL=, =terminal=, or a =configuration file=.
Remember: for debugging or displaying output, it's best to run Guile from the =REPL= or =terminal=. You can also pipe the output to a file if you desire.
2- I plan to publish a module for =swayipc=, it's currently not hosted anywhere. You'll need to add the module to your =load path=. Additionally, =swayipc= includes another patched Guile library called =guile-json=, which is embedded for now. In the future, this will be included as a separate dependency rather than embedded.
I arrange my workspaces in a grid format. Typically, workspaces are laid out horizontally. With nine workspaces, navigating from workspace 1 to 9 using only horizontal directions can be cumbersome. Assigning a key to each workspace would be efficient but would clutter default mode keybindings. Some might create another mode or submap, but pressing multiple keys to move between workspaces becomes inefficient . I find the optimal solution is organizing workspaces in a grid format, enabling both horizontal and vertical navigation. Currently, I use a 3x3 grid with wraparound navigation.
Horizontal vs Grid 9 workspaces
Horizontal
#+begin_src
1 2 3 4 5 6 7 8 9
#+end_src
Grid (3x3)
#+begin_src
1 2 3
4 5 6
7 8 9
#+end_src
Example navigation in a grid (=cs#idx= is current workspace):
#+begin_src
cs#1> go right
cs#2> go down
cs#5> go down
cs#8> go down (notice wraparound behavior)
cs#2> go right
cs#3> ..
#+end_src
Note: this behavior is achieved via =modules/workspace-grid.scm=
*** Workspace Groups
My workspaces function as groups or tasks that span across all three monitors in my setup. For example, if I switch to my =communication= workspace on one monitor, I want all monitors to switch to their respective =communication= workspaces. This means if I have WhatsApp on monitor #1, Discord on monitor #2, and IRC on monitor #3, they should all align to their designated communication workspace when I switch tasks.
Similarly, this setup extends to projects I work on. If I focus on my dotfiles, I want all monitors to switch to the workspace dedicated to that task. The same principle applies to game development or any other specific task or project workspace I engage with.
Example of navigation into a workspace (same behavior regardless of the method used to switch workspaces):
#+begin_src
ws#1> go to ws#2-1
ws#2> go to ws#2-2 (same group, no switching)
ws#2> go to ws#1-3
ws#1> ..
#+end_src
You can partially configure workspace groups to span or sync only some workspaces. This allows you to have workspaces that do not span and others that do, with the ability to pin specific workspaces to their monitors when focused.