A bit of a high level change log of what we've done so far in the series and what's up next. This isn't a full project roadmap by any means but it should serve as a bit of a guide for where to find things and where we're heading.
NOTE: Code coming from the episodes should have commit messages prefixed with the episode number they came from so that you can look up where changes came from relatively easily.
ep42: found the meaning of life (bound to "M-S-/")
Bootstrapping our window manager
We went through setting up our initial main.rs
following the steps covered in the
getting started guide in the Penrose docsite (along with some helper scripts
to make things a little smoother to iterate on going forward). What we end up with
is pretty bare bones but it works and we have a starting point to start iterating
on our window manager!
- Initial crate with dependencies
- Copy in the
main.rs
from the minimal example in GitHub -
Makefile
and helper scripts - xsession file
Making things a little more comfortable to live in
Minimal really does mean minimal: there's not much here to start so lets hook up some quality of life improvements we can copy from the examples so that we have a decent working environment to start customising things.
- Add in EWMH hooks so that programs which need EWMH properties can find them
- Set up a simple status bar (also requires telling the layouts to reserve screen space)
- Take a look at layouts and the built in layout algorithms
- Logout / restart keybindings
Configuring keybindings
Now that we have something that's a little nicer to live in we can start digging in to specfic things work. This episode we're taking a look at keybindings: how they work, how to write custom KeyEventHandlers and what Penrose is doing behind the scenes with your bindings to update your window manager state.
- Reviewing the logout / restart keybindings we wrote last time
- Looking at the KeyEventHandler trait
- The key_handler helper function
- Looking at the source code of some of the built in actions
- Trait objects in Rust
- The modify_and_refresh method on
XConn
- Using dmenu to write ourselves a power menu
Hooking into window manager execution
In addition to running your own code in response to a key binding, you can also set up custom hooks to tell Penrose to run some additional logic when specific parts of the window manager event loop are hit. In this episode we take a look at what the different hook points are, the traits involved for writing hooks and we have a go at writing each kind of hook.
- Overview of how the window manager runs
- Details on why we need set the sigchild handler can be found here
- The different kinds of hooks we can write
- Adding a startup hook (execute a script)
- Adding a layout hook (spacing and gaps)
- Adding a manage hook (move a certain program to a specific workspace)
-
Refresh and event hooks in the status bar
State extensions, scratchpads and the statusbar
Sometimes you find yourself needing to make use of persistant state that is not provided
by the window manager itself. There are lots of ways to achieve this in Rust but one easy
way to track state which you are using for your bindings and hooks is to use a state extension
.
In this episode we take a look at what a state extension is, how to set one up and where
some of the code provided in the main Penrose crates makes use of them.
- What are state extensions?
- Why do you need these in the first place?
- Adding a NamedScratchpad
Writing status bar widgets
The status bar provided by the penrose-ui
crate allows you to write simple text based widgets
which you can drive from your window manager state or external data sources. The default set up
is configured to mimic the status bar from dwm
with the root window name being used as a way
of placing arbitrary text in your status bar. The widget support in penrose-ui
extends
this idea to allow you to drive individual sections of the bar using custom code.
In this episode we go over how the status bar works and how you can write your own widgets to
extend the behaviour.
- Taking a look at the penrose-ui crate
- How widgets work
- Rewriting our status bar to customise which widgets we use
- Writing a our own simple widget using IntervalText
Writing a custom layout
One of the most appealing things about a tiling window manager is having layout algorithms automatically position your windows for you on the screen. Penrose comes with a few simple algorithms out of the box and in this episode we'll write a new one, show how to work with the Rect struct to easily divide up screen space.
- A quick look at the Layout trait.
- What the Penrose book says about layouts
- Writing a fibonacci layout (inspired by this dwm patch)
- We'll be using the print_layout_result helper for this
- The entr program I use to auto-run our example file when changes are made can be found here.
Handling layout messages and fun with layout transformers
As we've already seen with the MainAndStack
layout, it is possible to modify how the
active layout is working by sending it Messages. These allow us to bind keys to
changes in our layout behaviour in pretty much any way that we want!
For layouts that are not implemented by us, there is the LayoutTransformer trait
which allows for per-layout modifications to how layouts run (similar to a layout hook).
There are a couple of built-in layout transformers available to use but it is also possible
to hand write our own higher-order layouts in order to really customise how things work.
- Sending dynamically typed messages to layouts
- Handling messages in our layout
- Applying layout transformers to existing layouts
- Writing a custom meta-layout because we can
- This last one might sound intimidating but it's actually pretty easy!
A look at the "pure state" of Penrose
The internal design of Penrose following the 0.3 rewrite is heavily inspired by the approach taken by XMonad (as opposed to dwm which was the case in the original design). The main idea is to split the core of the library into two parts:
- Pure data structures that have nothing to do with X code
- Handler code and API wrappers for interfacing with the X server
Today, we're going to have a look at the pure data structures and how they operate. They are based on a technique called Zippers which are a way of encoding the concept of a focused element within a larger collection-like data structure.
The penrose book in GitHub Pages has more information on how all of this works:
- https://sminez.github.io/penrose/overview/pure-vs-x.html
- https://sminez.github.io/penrose/overview/data-structures.html
The data structures used by Penrose are heavily inspired by those found in XMonad: if you are happy reading Haskell then the following links are worth a look!
-
https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf
-
https://donsbot.com/2007/05/17/roll-your-own-window-manager-tracking-focus-with-a-zipper/
-
What is a zipper?
-
Why not just use
Vec
? -
Building up the tree state for a StackSet