Emacs already has more workspace management packages than I can count. In the past, I exclusively used workgroups2 because no other package had all of the functionality I consider important. These are the main features supported by this package:
- Workspace-local/independent window layout history/undo (this is the feature that no package I am aware of besides workgroups2 supports and is the primary reason I created this package)
- Named, not (just) numbered, workspaces; each represent some context (e.g. book notes, programming project, etc.)
- Hooks for creating and switching between workspaces to allow default session setup and so that workspace-specific keybindings/settings can be applied
- No limit to the number of workspaces (elscreen is the main and possibly only offender)
- Persistence (this is generally not an issue as desktop.el can be used to store sessions)
While workgroups2 has all of the above functionality, it has serious issues as well:
- It is unmaintained and cannot save layouts on the latest Emacs
- Extra configuration is necessary to fix some annoyances:
- Golden ratio/zoom must be temporarily disabled when switching workgroups to prevent “window too small for splitting” errors when switching to a workgroup with more than two or three windows (this is also an issue for some alternative packages I’ve tried)
- Window resizing (e.g. automatically done by golden ratio/zoom) is recorded by default (moving through window size history is pretty useless, especially if you are using a package like golden ratio;
winner-mode
, on the other hand, doesn’t record window resizing)
Workgroups2 is a fairly large package, and I don’t want to attempt to maintain/fix it when I only need some of its functionality. The goal of this package is to be as simple as alternatives while also allowing for the useful functionality they lack. The main principle is to use multiple frames as workspaces instead switching between window layouts inside a single frame. This has several advantages:
winner-mode
keeps track of window configuration changes separately for each framedesktop.el
can be used to persist and restore all frames- The user can change keybindings based on the frame’s
fg-name
property (e.g. by usingfg-after-switch-hook
) and make use of frame-local variables - No “window too small for splitting” issues
The primary issue with using frames for workgroups-like functionality is that emacs (by itself) has no way of displaying only one frame at a time and hiding the others. This isn’t an issue if you are willing to dedicate different desktops to different frames. On the other hand, frames aren’t great if you want a lot of contexts/groups or would prefer to use keys bound in emacs for switching between them instead of always using global hotkeys (since select-frame-set-input-focus
does not work across desktops).
By default, this package attempts address these issues issue by using xdotool
to hide the old frame and switch to a new one. If xelb is able to easily map/unmap windows, I may switch to using it in the future.
Note that this package will only attempt to use xdotool if the user is using X11, xdotool is installed, and the related settings (see below) are non-nil. Otherwise, it will not attempt to hide frames and will use emacs’ builtin frame switching function.
To clarify, if you are not using Linux/X11 or do not wish to use xdotool, most of this package’s functionality is still available:
fg-rename-frame
worksfg-create-frame
worksfg-create-hook
works- the mode line indicator works
fg-switch-to-frame
andfg-switch-to-last-frame
will likely not work across desktops (you’ll need to use global WM keys to switch between desktops); they should work for frames located on the same desktopfg-after-switch-hook
is only triggered fromfg-switch-to-(last-)frame
(you can usefocus-in-hook
instead if you want to dedicate different WM desktops to different frames)fg-desktop-setup
will not work
In summary, reliable frame switching when using multiple desktops requires xdotool (basically a hack). Using xdotool to hide/switch windows can also cause flicker especially if you are not using a compositor. That said, if all your frames are on a single desktop, using xdotool is not necessary. You can instead have only one frame visible by using the monocle/fullscreen layout for that desktop.
The following sections use the term “framegroup” to refer to a frame that this package is managing (i.e. it was named/focused using this package’s commands).
fg-hide-with-xdotool
(default t): whether to hide the old frame when switching to a new one (only has an effect when using X11 and xdotool is installed)fg-switch-with-xdotool
(default t): whether to switch frames using xdotool instead of emacs’select-frame-set-input-focus
(only has an effect when using X11 and xdotool is installed andfg-hide-with-xdotool
is nil; whenfg-hide-with-xdotool
is non-nil, xdotool must be used to remap hidden windows)fg-auto-create
(default t): whether to automatically create a framegroup when attempting to switch to a non-existent onefg-create-hook
: hook run when a framegroup is first created (renaming also triggers this hook)fg-after-switch-hook
: hook run after switching framegroups; functions are run with the name of the new framegroup as an argument
Commands:
fg-rename-frame
: set the name of the current framegroupfg-create-frame
: choose a name and create a new framegroupfg-switch-to-frame
: switch to another framegroupfg-switch-to-last-frame
: switch to the previously focused framegroup
Helper functions/macros:
fg-switch
: a convenience macro that returns a function that will switch to a framegroup (e.g.(define-key "key" (fg-switch "notes"))
)fg-desktop-setup
: will add todesktop-read-hook
to hide all but the last focused frame when restoring a sessionfg-mode-line-string
: return the current framegroup name formatted for the mode line
This package does not provide numbered workspaces or directional switching commands as I personally prefer to have named contexts. You could of course simply name your frames with numbers and create directional switching keybindings yourself with fg-switch
, but I do not plan to this functionality directly.
;; enable `winner-mode' for undo
(winner-mode)
(global-set-key "C-c l" 'winner-undo)
(global-set-key "C-c L" 'winner-redo)
;; enable `desktop-save-mode' for persistence
;; NOTE: It seems Emacs occasionally hangs when restoring a lot of frames with
;; desktop.el
(fg-desktop-setup)
(desktop-save-mode)
;; binding keys to switch to specific framegroups
(global-set-key "C-c e" (fg-switch "emacs"))
(global-set-key "C-c p" (fg-switch "prog"))
;; ...
;; default layouts for framegroups
(defun my-framegroup-setup (name &rest _)
"Set up default framegroup layouts."
(interactive)
(pcase name
;; emacs configuration
("emacs"
(find-file "~/.emacs.d/other.el")
(split-window-right)
(find-file "~/.emacs.d/init.el"))
;; programming projects
("prog"
(find-file "~/src"))
;; dotfiles
("config"
(find-file "~/dotfiles"))
("mail"
(mu4e))
("music"
(mingus))))
(add-hook 'fg-create-hook #'my-framegroup-setup)
;; binding keys for the current framegroup
(defmacro my-ff (file)
"Wrapper for creating `find-file' commands."
`(lambda () (interactive) (find-file ,file)))
(defun my-framegroup-keybindings (name &rest _)
(pcase name
("emacs"
(global-set-key "C-c , i" (my-ff "~/.emacs.d/init.el")))
("prog"
(global-set-key "C-c , r" (my-ff "README.org"))
(global-set-key "C-c , d" #'projectile-edit-dir-locals))))
(add-hook 'fg-after-switch-hook #'my-framegroup-keybindings)
For mode line integration, you can insert fg-mode-line-string
into the mode line:
(setq-default mode-line-format
;; ...
'(:eval (when (fboundp 'fg-mode-line-string)
(fg-mode-line-string)))
;; ...
)