Moving from KDE to Emacs

When I started using EXWM, I thought that using it together with KDE would be the best combination. But then my Plasma configuration self-destructed by chance, I had to recover it from backup and I started thinking what and how much I actually need from KDE exactly.

The basic principle is simple: Things are easier to do in KDE but what you get is basically all you get. Arranging the complete desktop in Emacs would mean knowing what’s happening and making it flexible, for the price of more initial work. I decided to try it and I switched from KDE to Emacs completely.

Here is what I had to do to make it happen.

Key bindings

This is the easiest part. All key bindings are simply managed by Emacs.

Multiple keyboards

Emacs input methods, which are much better and simpler to customize than X keyboards, can be used in applications supporting XIM. Mainstream applications generally work with Emacs input methods. setxkbmap can be used for those that don’t if needed.

I have already discussed setting up EXWM for input methods in a previous post.

Multiple monitors

This is harder. In KDE, it works some way automatically and there is a user friendly configuration tool. Although it may happen the configured settings change from time to time and the configuration must be fixed. EXWM works well together with KDE; without it, it must be handled using other tools.

On the contrary to what is written in EXWM documentation, no exwm-randr-screen-change-hook arrangements were needed. But there is still configuration needed. Especially when changing to a new monitor setup for the first time, the given setup must be arranged manually using xrandr, ARandR, or another tool. Then it can be saved using autorandr.

In order to make switching between the configured monitor setups automatically on monitor connections and disconnections, and when opening or closing the laptop lid, system environment must be configured accordingly. This is what works for me on NixOS:

services.autorandr.enable = true;
services.acpid.enable = true;
services.acpid.lidEventCommands = "/run/current-system/sw/bin/autorandr --batch --change";

It’s also highly advisable to configure exwm-randr-workspace-monitor-plist accordingly.

Desktop notifications

Emacs Desktop Notification Center (EDNC) handles them and opens new amazing possibilities to process them. I have already discussed it in a previous post. Obviously, KDE absence avoids the conflict with KDE notification handling and it’s no longer needed to apply the plasmashell workaround.

Panel

Panel widgets provide useful functionality such as displaying information about the system or access to various settings.

There are several Emacs add-ons to display system information. I got attracted by lemon, which allows displaying information in the echo area when Emacs is idle. This is a good use of an otherwise unused screen area. But it’s not without problems. The set of provided monitors in lemon is limited, due to EIEIO limitations it’s not always easy to customize the provided monitors or even to customize my own monitors (broken multiple inheritance!), it’s uncomfortable both waiting for the lemon output to appear and having it appearing prematurely, replacing minibuffer or echo area contents (e.g. in isearch or when a function help or an evaluated expression is displayed), and lemon may stop updating the echo area when in a non-Emacs application buffer. I still think lemon is a great idea but it doesn’t match my needs well enough.

So I decided to abandon lemon and to sacrifice one line of the screen space and use it for the tab bar. I use tabs, but I don’t display them in the tab bar, I use the tab bar for displaying global information (e.g. sound settings, system info, notifications, time, battery status). It’s possible to use the mode line for such purposes, but there is much less space there and I keep only buffer-specific information there, which makes more sense.

One problem with using the tab bar is that if something you put there produces an error, even once, then the tab bar disappears and cannot be restored until Emacs is restarted. Annoying.

System settings

Without the KDE panel, an alternative access to some system settings is needed.

As for network, I use nmcli now. It’s not that bad as it sounds and with a couple of Eshell aliases for common actions (handling WiFi, VPN, …), it may be more comfortable in some sense than using the dialogs. And network connections can be still edited using GUI tools if needed.

As for Bluetooth, I use Emacs Bluetooth Mode. It has some quirks but Bluetooth setup has never been easy in (not only) GNU/Linux, in any environment.

As for sound, I had to write my own Emacs add-on, pipewire-0, to manage PipeWire settings. I tailored it for my own needs, so it serves me well. 🙂

GnuPG password entry

Whether using KDE or not, GnuPG password entry doesn’t work well with EXWM when using external dialogs. Several EXWM tutorials contain tips, not exactly the same, how to fix it. Here is my version.

Emacs pinentry must be enabled by putting

allow-emacs-pinentry

line to ~/.gnupg/gpg-agent.conf.

NixOS has a corresponding configuration option (and pinentry-emacs package):

programs.gnupg.agent.pinentryFlavor = "emacs";

And an option to start the agent itself:

programs.gnupg.agent.enable = true;

Finally, Emacs pinentry must be configured in Emacs configuration:

(setenv "GPG_AGENT_INFO" nil)           ; when not using the NixOS option
(use-package pinentry
  :init
  (setq epg-pinentry-mode 'loopback)
  :config
  (pinentry-start))

Using Java applications

Java is infamous for its window manager related problems. Fortunately, it contains a workaround that can be activated by setting an environment variable:

export _JAVA_AWT_WM_NONREPARENTING=1

Starting the desktop

It’s distribution dependent and it was tricky to find it out on NixOS. In theory, a simple setting

services.xserver.windowManager.exwm.enable = true;

might work. But since NixOS makes hard to access additional C libraries and Python packages from Emacs, I must start Emacs with nix-shell, with a help of my custom shell script. Instead of the setting above, I must use the following:

services.xserver.displayManager.session = [
  { manage = "desktop";
    name = "emacs";
    start = ''
      /my/Emacs/startup/script.sh &
      waitPID=$!
    '';
  }
];
services.xserver.displayManager.defaultSession = "emacs";

Autostarting applications after logging in

This can be arranged in various, less or more smart, ways in ~./emacs.d/ using start-process-shell-command.

Now, when applications are always started from Emacs, they are guaranteed to get down together with it. This has two problems.

First, throughout the years with Emacs, I got used to understand the Emacs confirmation question about terminating running processes on exit just as a protection against accidentally hitting C-x C-c. I respond “yes” automatically and this terminates applications forcefully. I must learn to quit them cleanly with kill-buffer before exiting Emacs.

Second, Emacs restart requires restarting the other applications too. This is quite annoying although actually not that big deal since the only non-Emacs application I run most of the time nowadays is a web browser.

Screen saver

There are several screen savers available. I tried using i3lock with xautolock but it didn’t work very well, the screen could get blanked during meetings or while watching videos. The old good XScreenSaver works better although autolocking and switching screen off automatically doesn’t seem to work completely as expected.

Conclusion

My desktop is completely managed by Emacs now (except for the log in screen). I like it, it’s a clean environment, under my full control. It was a non-trivial effort though and it’s always tempting to improve something (unlike with KDE where it’s not possible). It depends on one’s needs whether it’s worth it or not. For us who have Emacs as a hobby, perhaps it is; the others may happily continue to use Emacs with KDE.

emacs-desktop.jpg

More fun with EXWM

I have recently started using EXWM, an Emacs window manager. It’s very useful as a window manager itself but it also provides additional functionality and opens new possibilities.

Starting EXWM automatically in KDE

EXWM allows, similarly to other window managers, replacing an already running window manager. Now I’m confident about it enough and start it automatically, by creating ~/.config/plasma-workspace/env/exwm.sh file with the following content:

export KDEWM=~/bin/start-emacs.sh

Why to use a script rather than starting Emacs directly? We’ll see later.

EXWM and input methods

EXWM allows transforming keys sent to applications. I don’t have currently a good use case for transforming key shortcuts or so but I’m absolutely amazed by the possibility to use Emacs input methods in X applications.

The predefined X.org keyboard layouts are far from perfect and everybody who tried to use a customized keyboard using XKB knows the related problems. On the other hand, defining custom keyboard layouts in Emacs is relatively easy, doesn’t require changing system configuration, and offers significantly more flexibility.

Yes, with EXWM, it’s possible to use Emacs input methods in other applications! The following must be added to the exwm.sh file created above:

export XMODIFIERS=@im=exwm-xim
export GTK_IM_MODULE=xim
export QT_IM_MODULE=xim
export CLUTTER_IM_MODULE=xim

On the Emacs side, it’s desirable to add the shortcut for switching input methods, before EXWM is started:

(push ?\C-\\ exwm-input-prefix-keys)

And the last thing needed is to enable exwm-xim:

(autoload 'exwm-xim-enable "exwm-xim")
(exwm-xim-enable)

Using Emacs input methods in e.g. a web browser makes typing there much simpler (at least for those of us who use two or more languages). Then the only missing thing is the possibility to use different input methods in different pages or tabs displayed in the browser, similarly to Emacs buffers. (Spoiler: There is a non-Emacs web browser that allows doing this.)

Editing input in Emacs

It’s also possible to edit input fields in applications directly in Emacs using exwm-edit package. It’s another useful feature but it must be used with some caution, the edited text may get lost e.g. when the input widget in the application is no longer focused once the input text is sent to the application.

Desktop notifications

I use EXWM together with KDE. There is no reason to replace absolutely everything with Emacs. At the dreary times when GNOME was already unusable and KDE 4 not yet usable, I used to use StumpWM and I really missed things such as a normal panel with its widgets and easy access to functions such as network configuration, or a control center for settings.

But one KDE functionality (besides kwin) is worth to move to Emacs: desktop notifications. The desktop notifications do their job in KDE, but nothing more. The notifications are displayed for a while in a popup window and they are also later (not so well) accessible in the panel. But can you easily identify what’s new when looking at the screen at any given moment? Absolutely no. It very calls for a better notification management. Imagine you want to dismiss all e-mail notifications when you switch to your e-mail client. Or you are forced to use a crappy proprietary chat application without a usable API, accessible only via a web browser interface, and providing useful information to the outer world only in the form of temporarily displayed notifications (this is unfortunately a real example!). Etc.

Here the Emacs Desktop Notification Center (EDNC) comes to help. Just enable it:

(use-package ednc)
(ednc-mode 1)

and then you have all the notifications in your hands. Display, format, close, do whatever you want with them in the ways you like. Very useful and another step to a distraction free desktop. No need to use a secondary screen to watch your e-mail or chat messages, or polling their presence by switching to the corresponding applications regularly. With EXWM, one never leaves Emacs, the mode line is always here and notifications about desktop notifications can be displayed there. Now you can detect easily which inbox has new mail or who wrote a chat message or that something else happened. The details are then accessible in the EDNC log buffer or elsewhere, only once you want to see them. And it may be another use case for pip-frame.el, to display selected incoming notifications temporarily.

There is one problem though. I haven’t found a good way to disable the KDE notification service. And while it is running, EDNC cannot be started. I use this ugly (but working) hack:

(shell-command "kill $(pgrep plasmashell)")
(ednc-mode 1)
(start-process-shell-command "plasmashell" nil "plasmashell")

When plasmashell is killed, the notification service is stopped and EDNC can be started. Then plasmashell can be started again.

But this means plasmashell is started from Emacs and is taken down when Emacs is finished. This is not what I want and it is the reason why I use a script in KDEWM environment variable. The script not only starts Emacs but it also starts plasmashell again when Emacs is finished.

Using EXWM

I sometimes miss useful things. For example, many years ago, I missed emerging Org Mode, not recognizing its potential, only to discover some years later how essential tool it is. And the same happened to me with EXWM, an Emacs window manager.

I’ve always thought that making a window manager in Emacs is a crazy cool idea, which is not very useful in practice. If anything bad happens to Emacs or Emacs needs to be restarted then the whole desktop goes down. It may be a good reason itself to avoid it and there is an additional question why would one use it instead of a standard well tuned window manager. I use a tiling approach to window management, with a single window displayed over the whole screen most of the time, and I don’t need much functionality from the window manager after all.

As with many Emacs tools, there is a very good reason to use it. What I missed (my fault, discarding the idea before looking at it properly) was that windows in EXWM behave similarly to regular Emacs buffers. And Emacs already has a powerful built-in window management.

I discovered EXWM because I looked for a way to easily display a web browser window (or occasionally some other application window) side by side with an Emacs frame. This is not difficult to achieve but what if I want to switch to another Emacs workspace and back? What if I want to pop up a help window instead of the web browser window and then return back to the browser? Etc. It starts to be complicated even with advanced tiling window managers like StumpWM. (And no, window manager desktops are not a solution.) While Emacs provides similar functionalities instantly with pop-to-buffer and all the other window management facilities. When realizing this all, I couldn’t understand how I could live without EXWM. Even when not considering added bonuses, such as the possibility to use normal key bindings (compared to e.g. KDE key bindings limited to single-key bindings only and unable to use Super and Hyper modifiers).

And how about the supposed problems? Emacs usually doesn’t crash and also doesn’t block that often. In practice, there don’t seem to be real problems. Whether other applications must be restarted on Emacs restart depends on whether they are started from Emacs or outside it, which is actually not different from using other window managers.

I use Emacs / EXWM just as a replacement for the window manager, not the whole desktop environment. I keep using KDE while using EXWM instead of kwin. This means I can still use panels (with some limitations such as that it’s not possible to use autohiding panels and it’s possible to configure the panels only when EXWM is not running), general key bindings managed by KDE (useful, among other, to be able to run basic applications when Emacs is not running), KDE settings, multiple monitors (they work well without further arrangements in this environment), notifications, etc.

When setting up the EXWM environment, I realized I need a PIP (picture in picture) facility for Emacs buffers. I couldn’t find anything like this, so I wrote a simple utility called pip-frame.el implementing a floating frame displaying Emacs buffers.

Emacs tabs as workspaces

I used to use Emacs frames as workspaces. But reading through Emacs 28 NEWS reminded me that tab bars could be probably better used for the purpose. And indeed, using tab bars is simpler and looks like a very good fit.

First some initial settings:

(tab-bar-mode 1)
(tab-bar-history-mode 1)
(custom-set-variables '(tab-bar-show nil))
(tab-bar-rename-tab "emacs")

They enable the tab bar mode, tab bar history mode (not needed but useful) and hide the tab bar (not to waste screen space for no good purpose). We also want to give a static name to the initial tab.

Now we need to retrieve the tab names. I don’t know whether there is an official way to do it but the following functions serve the purpose:

(defun my-workspace-name ()
  (alist-get 'name (assq 'current-tab (funcall tab-bar-tabs-function))))

(defun my-workspace-all-names ()
  (mapcar #'(lambda (tab) (alist-get 'name tab))
          (funcall tab-bar-tabs-function)))

Workspace switching is much simpler with tabs:

(defun my-workspace-switch (name)
  (interactive "sWorkspace: ")
  (if (member name (my-workspace-all-names))
      (progn
        (tab-bar-switch-to-tab name)
        nil)
    (tab-bar-new-tab)
    (tab-bar-rename-tab name)
    t))

There is no need to track and handle frames anymore. Switching to the last workspace can be done simply with tab-bar-switch-to-recent-tab function.

The rest remains the same as previously with the frames:

(defvar my-workspaces
  ;; name
  ;; new-fn
  ;; refresh-fn
  '(("emacs"
     nil
     (lambda ()
       (require 'bookmark)
       (unless bookmark-alist
         (bookmark-maybe-load-default-file))
       (bookmark-jump "init.d"))) 
    ("gnus"
     gnus
     (lambda ()
       (let ((group-buffer (get-buffer "*Group*")))
         (if group-buffer
             (switch-to-buffer group-buffer)
           (gnus)
           (cd "~")))))
    ("irc"
     my-erc-connect
     nil)
    ("org"
     my-org-agenda
     my-org-agenda)
    ("roam"
     org-roam-find-file
     org-roam-find-file)
    ("system"
     my-run-eshell
     (lambda ()
       (call-interactively
        (lambda ()
          (interactive)
          (ido-buffer-internal ido-default-buffer-method nil nil nil "eshell:")))))))

(defun my-switch-to-workspace (&optional key)
  (interactive)
  (unless key
    (setq key (logand last-command-event 255)))
  (let* ((prefix (char-to-string key))
         (current-name (my-workspace-name))
         ;; This will be explained later:
         (predefined (cl-assoc prefix my-workspaces :test #'string-prefix-p)))
    (if predefined
        (cl-destructuring-bind (name new-fn refresh-fn) predefined
          (if (equal current-name name)
              (when refresh-fn
                (funcall refresh-fn))
            (when (and (my-workspace-switch name)
                       new-fn)
              (funcall new-fn))))
      (let* ((candidates
              (remove current-name
                      (cl-remove-if-not
                       #'(lambda (n) (string-prefix-p prefix n))
                       (my-all-workspace-names))))
             (n (length candidates)))
        (cond
         ((= n 0)
          (my-workspace-switch
           (read-from-minibuffer "Switch to workspace: ")))
         ((= n 1)
          (my-workspace-switch
           (car candidates)))
         (t
          (my-workspace-switch
           (completing-read "Switch to workspace: " candidates nil t prefix))))))))

(global-set-key (kbd "<s-return>") 'my-last-workspace)
(dotimes (i 26)
  (global-set-key (kbd (format "s-%c" (+ ?a i))) 'my-switch-to-workspace))

(defun my-switch-to-workspace-key (key)
  (interactive "c")
  (my-switch-to-workspace key))  
(global-set-key (kbd "C-c z") 'my-switch-to-workspace-key)

Of course, it’s possible to use tabs as workspaces directly, without these add-ons and with taking advantage of tab-bar-tab-post-open-functions variable. Especially people who like using mouse for tab switching may like using the tab bar mode as it is. But I still like the bits of extra functionality for a bit more comfortable tab/workspace switching.

Software problem: Compiling Emacs vterm module on NixOS

I started using NixOS again some time ago. The fact that NixOS doesn’t use Filesystem Hierarchy Standard is sometimes a big source of pain and complications. One non-obvious thing for beginners is how to compile a C program.

One C program that is very useful and must be compiled locally is emacs-libvterm C module, providing a visual terminal for Emacs. There are several good reasons to use emacs-libvterm:

  • It’s sometimes useful to run visual terminal programs.
  • Many command line programs don’t work well with line terminals.
  • If a command line program uses its own command line interface, it’s occasionally useful to take advantage of it. Additionally, Eshell command line editing doesn’t work with programs that enter their own command line interfaces.
  • External, non-Emacs, terminal emulators work well but their capabilities are quite limited and they are not integrated with Emacs.
  • Built-in Emacs visual terminal emulators (term, ansi-term) don’t work very well and are unusable with many applications and utilities.

emacs-libvterm solves all the problems. It works, it can use all the Emacs editing capabilities and it is integrated with Emacs. On standard systems, emacs-libvterm compiles its module automatically, as long as libvterm development package is installed. But it’s more difficult on NixOS.

The first problem is how to compile a C program on NixOS. As far as I understand it, nix-shell environment must be used. The second problem is that libvterm package in NixOS contains something old and apparently unmaintained and libvterm-neovim must be used instead. It took me quite long to realize it, I wasted a lot of time investigating problems related to libvterm and possibly other broken NixOS packages. Which was a problem of category 1, my ignorance, combined with a problem of category 2, broken obsolete packages in the distribution.

So how to compile Emacs vterm module on NixOS? Assuming gcc and gnumake packages are already installed on the system, it’s needed to create the following shell.nix file:

with import <nixpkgs> {};
stdenv.mkDerivation {
  name = "emacsenv";
  nativeBuildInputs = [ cmake ];
  buildInputs = [ libvterm-neovim ];
}

Then nix-shell is run in the same directory. When Emacs is started in the given nix-shell environment, emacs-libvtem compiles and installs fine.

Software problem: Emacs windows not properly maximized

I don’t like wasting screen space so the very first configuration action I do in newly installed desktop environments is making all windows maximized by default.

After I had reinstalled my computer, I experienced a problem that new Emacs windows (or frames, in Emacs terminology) created after Emacs had started were not fully vertically maximized, being about two lines shorter than they should. It can be easily remedied by unmaximizing and then maximizing them again but since I use lots of Emacs windows it’s quite annoying to do that all the time.

Some initial observations were:

  • The problem happened only for Emacs windows, not for other applications.
  • The same Emacs version running on a different computer with a different KDE version and on the same monitor worked fine.

This was weird – is it a bug in Emacs or in KDE or in combination of both? Or was it an environment issue? I also suspected Emacs could get confused by a font change during startup or some other configuration change. But running emacs -q proved the problem happens also in the default configuration.

Searching the web also didn’t help. Having no better choice, I tried miscellaneous actions in Emacs until I’ve discovered that the following Emacs setting fixes the problem:

(setq frame-resize-pixelwise t)

Which doesn’t make much sense because:

  • The initial Emacs window is all right.
  • The additional Emacs windows were shorter by more than one line.
  • When a short window was unmaximized and maximized again, it got properly maximized.

So it looks like a bug, i.e. software problem of category 3. It’s hard to say where though and it may be bound to special environment properties so it would probably require a lot of time trying to report it. The workaround is good enough and I like the given setting anyway.

Emacs workspaces

I use Emacs for everything that can be done with it reasonably. I often run in a single Emacs session Gnus, Org agendas, command line actions, IRC, several development projects, etc. The question is how to switch between all those activities efficiently.

During the years, I tried several workspace, perspective or screen (whatever they are called) Emacs add-on packages. But I have never been completely satisfied with any of them. There have always been bugs, limitations and/or discomfort.

Then I applied the principle of simplicity: Use the simplest thing that may work. And the thing is Emacs frames. As simple as that, I’ve been using my workspaces based on Emacs frames for almost two years now and it has been working to my satisfaction so far.

The frames provide two crucial facilities:

  • Separate dynamic window configurations and switching between them easily. Frames provide that by definition. The only problem is that your window manager must be able to manage a lot of Emacs frames reasonably. But any civilized window manager should be able to do that and e.g. KDE is.
  • Switching to workspace specific buffers. Frames do that by default — at least with Ido, recently used buffers in the given frame are offered first when switching buffers. Unlike in some other workspace solutions, the buffers offered are not limited just to the given workspace, which is a killer feature. It’s much easier not to care about assignments of buffers to workspaces and to simply rely on the recent buffers approach.

Of course, C-x 5 key bindings are not enough to work with workspaces efficiently. I implemented my own workspace management mechanism, based on my wrappers around the previous solutions I used and described below.

First, it’s useful to track the last frame because returning to the last workspace is a very common action:

(defvar my-last-frame nil)

Let’s track last workspaces too:

(defvar my-last-workspaces '())

Frame switching may not work smoothly in some window environments, so let’s make a custom function to select a frame and to store the workspace to the variables defined above:

(defun my-select-frame (frame)
  (unless (eq frame (selected-frame))
    (setq my-last-frame (selected-frame)))
  (raise-frame frame)
  (x-focus-frame frame)
  (select-frame frame)
  (setq my-last-workspaces (cons frame (remove frame my-last-workspaces))))

I give my workspaces names, stored and displayed in frame titles. Let’s define functions retrieving workspace names:

(defun my-workspace-name (&optional frame)
  (let ((frame (or frame (selected-frame))))
    (or (frame-parameter frame 'title)
        (frame-parameter frame 'name))))

(defun my-all-workspace-names ()
  (setq my-last-workspaces (cl-delete-if-not #'frame-live-p my-last-workspaces))
  (mapcar 'my-workspace-name
          (delete-dups (append my-last-workspaces (frame-list)))))

Now we can switch among workspaces:

(defun my-workspace-switch (name)
  (interactive "sWorkspace: ")
  (let ((frame-list (frame-list))
        (frame nil)
        (new nil))
    (while (and (not frame) frame-list)
      (if (string= (my-workspace-name (car frame-list)) name)
          (setq frame (car frame-list))
        (setq frame-list (cdr frame-list))))
    (unless frame
      (setq frame (make-frame `((title . ,name))))
      (setq new t))
    (my-select-frame frame)
    new))

(defun my-last-workspace ()
  (interactive)
  (if (and my-last-frame (frame-live-p my-last-frame))
      (my-select-frame my-last-frame)
    (my-workspace-switch (read-string "Switch to workspace: "))))

As you can see, it’s possible to switch to an already present workspace or to a new workspace, using the same command. But typing the workspace name each time is cumbersome, let’s make it easier:

(defun my-switch-to-workspace (&optional key)
  (interactive)
  (unless key
    (setq key (logand last-command-event 255)))
  (let* ((prefix (char-to-string key))
         (current-name (my-workspace-name))
         ;; This will be explained later:
         (predefined (cl-assoc prefix my-workspaces :test #'string-prefix-p)))
    (if predefined
        (cl-destructuring-bind (name new-fn refresh-fn) predefined
          (if (equal current-name name)
              (when refresh-fn
                (funcall refresh-fn))
            (when (and (my-workspace-switch name)
                       new-fn)
              (funcall new-fn))))
      (let* ((candidates
              (remove current-name
                      (cl-remove-if-not
                       #'(lambda (n) (string-prefix-p prefix n))
                       (my-all-workspace-names))))
             (n (length candidates)))
        (cond
         ((= n 0)
          (my-workspace-switch
           (read-from-minibuffer "Switch to workspace: ")))
         ((= n 1)
          (my-workspace-switch
           (car candidates)))
         (t
          (my-workspace-switch
           (completing-read "Switch to workspace: " candidates nil t prefix))))))))

(global-set-key (kbd "<s-return>") 'my-last-workspace)
(dotimes (i 26)
  (global-set-key (kbd (format "s-%c" (+ ?a i))) 'my-switch-to-workspace))

This implements a single key workspace switching and assigns it to key bindings. I use a–z letters, as seen above, together with Super modifier to switch between workspaces. I name my workspaces in such a way that the name of each of them preferably starts with a different letter, making this mechanism work very well. I also define C-c z binding to be able to switch among workspaces when Super key happens not working:

(defun my-switch-to-workspace-key (key)
  (interactive "c")
  (my-switch-to-workspace key))  
(global-set-key (kbd "C-c z") 'my-switch-to-workspace-key)

As there are Emacs applications I run often, in their own workspaces, it’s useful to have predefined workspaces:

(defvar my-workspaces
  ;; name
  ;; new-fn
  ;; refresh-fn
  '(("emacs"
     nil
     (lambda ()
       (require 'bookmark)
       (unless bookmark-alist
         (bookmark-maybe-load-default-file))
       (bookmark-jump "init.d"))) 
    ("gnus"
     gnus
     (lambda ()
       (let ((group-buffer (get-buffer "*Group*")))
         (if group-buffer
             (switch-to-buffer group-buffer)
           (gnus)
           (cd "~")))))
    ("irc"
     my-erc-connect
     nil)
    ("org"
     my-org-agenda
     my-org-agenda)
    ("roam"
     org-roam-find-file
     org-roam-find-file)
    ("system"
     my-run-eshell
     (lambda ()
       (call-interactively
        (lambda ()
          (interactive)
          (ido-buffer-internal ido-default-buffer-method nil nil nil "eshell:")))))))

Each entry in this list defines a workspace name, the function to run when the workspace is created and the function to run when switching to the workspace I’m already in (a “refresh” function), which is especially useful to reset the workspace to its default state. The corresponding part in my-switch-to-workspace above takes care of the predefined workspaces.

One last bit is handling workspaces containing development projects. It’s natural to name them after their source directories but that could quickly lead to initial letter collisions with other workspaces. I dedicated a special x: prefix to those workspaces. I use Projectile to manage my source directories and integrate it with my workspaces:

(defconst my-projectile-workspace-prefix "x:")

(defun my-projectile-switch-project-action ()
  (when (my-workspace-switch (concat my-projectile-workspace-prefix
                                     projectile-project-name))
    (projectile-vc)
    (delete-other-windows)))
(setq projectile-switch-project-action 'my-projectile-switch-project-action)  

When I open a development project using Projectile, it automatically switches to its workspace, names it accordingly and opens Magit for it. Then I can switch to the project using s-x key. If I work on multiple development projects simultaneously, my-switch-to-workspace offers the last one used by default to avoid using completion all the time.

As you can see, utilizing Emacs frames as workspaces is easy and powerful. I don’t need to use or make special add-ons to have workspaces, Emacs frames are good enough and they can be customized as needed relatively easily.

speechd-el 2.11 released

speechd-el 2.11 release introduces index marking feature, i.e. moving the cursor as the text of a buffer is read. This feature has been planned for more than 10 years but due to various circumstances, its implementation, although not that difficult, has been delayed. Now it is finally implemented although it may need further improvements in future if performance problems are experienced in practice. Sending long texts to Speech Dispatcher in chunks may be one of useful improvements.

speechd-el was designed as a project requiring low maintenance that needn’t be updated with each new Emacs version. Time has proved it is indeed so and speechd-el rarely requires updates of its code base. However, speechd-el has been here long enough to experience some conceptual changes in Emacs. During the time, Emacs has deprecated cl library in favor of cl-lib, improved the function advising mechanism and introduced a true lexical let. 2.11 version adjusts to those changes and, together with some other fixes, removed all compilation warnings.

So speechd-el is now in a clean state and the last long-planned feature has been implemented. I currently don’t plan any further changes to speechd-el unless there are bug reports, feature requests or patches from its users.

Capturing Web content from Firefox to Org

Emacs is a powerful tool but it’s better to use other means for Web browsing, such as Firefox. Now the question is how to transfer pieces of Web content from Firefox to Org mode. Org mode already provides means for communication with external applications. org-protocol.el is a general mechanism for importing information to Org mode via emacsclient, but its setup is not instant and I hadn’t bothered to configure it until I met org-protocol-capture-html. The screenshot of the captured content converted to Org markup was irresistible so I decided to give capturing Web content another try.

That attempt reminded me that I hadn’t ranted about software setup and bugs for quite long time here. I’m not going to fix that now, it suffices to say that utilizing a relatively simple function shouldn’t require advanced technical knowledge and/or several hours of googling and experimenting; I really can’t imagine how a non-advanced user could get that thing run without losing his patience at early stages of the process. Well, so I’ll try to make a summary of how I got it working.

Emacs part

I assume you already can use Org mode and emacsclient.

Plain text capture

Add org-protocol to org-modules variable.

Define entry for capturing Web content in org-capture-templates variable, e.g.:

(add-to-list 'org-capture-templates
             ("w" "Web site" entry (file "~/org/notes.org")
              "* %?\n%c\n%:initial"))

Of course, this is just an example. Look at org-protocol.el documentation for another example and for explanation what %:initial means.

If you’d like to use a letter different from w for the template, you can do so but you must replace it in Firefox bookmarklets and helpers below. See also org-protocol-default-template-key variable.

Capture with HTML conversion

First, configure plain text capture as described above. Then fetch org-protocol-capture-html.el from its home page and put it into your site-lisp directory. Add the following lines to your ~/.emacs or other Emacs initialization file:

(require 'org-protocol)
(require 'org-protocol-capture-html)

Note that org-protocol must be already loaded at the time org-protocol-capture-html is loaded, otherwise the corresponding subprotocol won’t be registered.

Firefox part

The easy way

Install Org-capture for Firefox. It allows capturing content without the need to register org-protocol: handler in Firefox. However, org-protocol-capture-html or other custom captures won’t work this way.

The advanced way

The following installs universal capturing mechanism via org-protocol: handler in Firefox. It works independently (with or without it) of Org-capture Firefox extension mentioned above.

Register org-protocol: handler as described in MozillaZine Knowledge Base (replace foo with org-protocol). One important thing they forgot to emphasize is that you must use real link to invoke the application dialog, typing org-protocol:something into the address bar doesn’t work. For your convenience, I provide an org-protocol link here. Select something like /usr/bin/emacsclient in the Firefox dialog as the application handling org-protocol.

Then define your capturing bookmarklets. If you don’t have Bookmarks Toolbar enabled, enable it by right clicking on a Firefox toolbar and selecting Bookmarks Toolbar. Then create new bookmark in Bookmarks Toolbar section and insert the following code as its URL:

javascript:location.href='org-protocol:/capture:/w/'+encodeURIComponent(location.href)+'/'+encodeURIComponent(document.title)+'/'+encodeURIComponent(window.getSelection())

This is for plain text capture. If you want HTML capture, define another toolbar bookmark and use the code from org-protocol-capture-html home page (it’s also available in the introductory comments in org-protocol-capture-html.el) as its URL. Just make sure that:

  • The bookmark URL starts with javascript:.
  • Pandoc is installed.

Now you can (optionally) select part of an HTML page and press one of the newly created bookmark buttons in Bookmarks Toolbar. If everything is set up correctly, the selected part of the page (or just page URL and title if nothing is selected) should appear in your Emacs capture buffer.

Getting rid of Bookmarks Toolbar

If you don’t use Bookmarks Toolbar in Firefox, you probably don’t want to waste screen space on it just for Org capture bookmarks. The remedy is easy, invoke Firefox Customize and drag Bookmarks Toolbar to another place. Alternatively, you can use Custom Buttons Firefox extension.

Notes

Some Web pages can’t be captured, I don’t know why. I have more important things to do than playing with Org and Firefox further.

Another useful Org related Firefox extension is Copy as Org-mode. It doesn’t capture content via org-protocol but can copy some objects such as page or link URLs to kill ring, in the Org format. This is what I used to insert links into this article! It’s easier with this nice helper than performing all the copy&paste&edit by hand.