I started looking into this today and made some interesting finds:
1. command-mode is almost exactly what I wanted -- except that half the binds seem to be missing. I found that *root-map* does not contain the default bindings when I look at it in my slime REPL.
2. handle-keymap (
https://github.com/stumpwm/stumpwm/blob/master/events.lisp#L212-L246) seems to be the ideal location to place logic that matches Bjergaard's suggestion. It's a recursive function that executes the key bindings. If it were prevented from leaving the top level of recursion, then it'd be the same as 'holding' the prefix key.
I'm going to look at implementing #2 (which I like more), but there were a few oddities with keymaps that I'd like to point out:
1. the bindings for ? and C-h are hard-coded into handle-keymap. I think the idea is that this prevents users from ever being stuck unable to do anything because PREFIX ? will always bring up the command list. However, this method of doing it strikes me as odd.
2. The command list (PREFIX ? or PREFIX C-h) lists all of my custom bindings + the defaults. However, when I eval *root-map* in the REPL it seems to contain only my custom bindings. When I do (push-top-map *root-map*) [what command-mode does], I can use my keys (eg 'b' for chromium, 'a' for evince) but not the defaults (eg 'n'/'C-n' for next-hidden-window).