-
Notifications
You must be signed in to change notification settings - Fork 18
Cookbook
This page collects useful configuration snippets.
emacsclient
allows you to edit a file in an already running instance of Emacs. This is handy both for opening a file from the command line and for use as your $EDITOR
when you're interacting with Git or similar programs. See more information about emacsclient
on the Emacs Wiki.
Using emacsclient
with EXWM is pretty straightforward, but you do need to start the server that the client will connect to. To start your server, call (server-start)
. This can either be done in your startup file, or manually once Emacs is running. The EXWM configuration example shows how to do it in the setup file.
Once you've got it set up, you can quickly open a file in Emacs from the command line by running emacsclient <path/to/file.txt>
. Similarly, if you've got emacsclient
set as your $EDITOR
and/or $VISUAL
environment variables, then programs such as Git will open a new buffer for you to edit in your current Emacs instance.
Running Emacs as a systemd service: If you've got Emacs set up to run as a system service, then EXWM won't create a new server on startup, because a server is already running. This creates a problem where emacsclient
can't find the server it's looking for and ends up either creating a new instance of Emacs or just not connecting. See this issue for more information.
You can use the same keybinding to change XKB layout in X11 windows and to set-input-method
in emacs windows.
(defvar exwm-input--numGroups 2)
(defun exwm-input--current-group ()
(interactive)
(slot-value
(xcb:+request-unchecked+reply exwm--connection
(make-instance
'xcb:xkb:GetState
:deviceSpec xcb:xkb:ID:UseCoreKbd))
'group))
(defun exwm-xkb-set-layout (num)
(interactive)
(xcb:+request-checked exwm--connection
(make-instance
'xcb:xkb:LatchLockState
:deviceSpec xcb:xkb:ID:UseCoreKbd
:affectModLocks 0
:modLocks 0
:lockGroup 1
:groupLock num
:affectModLatches 0
:latchGroup 0
:groupLatch 0))
(xcb:flush exwm--connection))
(defun exwm-xkb-next-layout (&optional inc)
(interactive)
(exwm-xkb-set-layout
(mod (+ (or inc 1) (exwm-input--current-group))
exwm-input--numGroups)))
(defun exwm-xkb-reset-layout ()
(interactive)
(exwm-xkb-set-layout 0))
(add-hook 'exwm-init-hook
(setq exwm-input--numGroups
(slot-value
(xcb:+request-unchecked+reply exwm--connection
(make-instance
'xcb:xkb:GetControls
:deviceSpec xcb:xkb:ID:UseCoreKbd))
'numGroups)))
(define-key exwm-mode-map
(kbd "C-\\") #'exwm-xkb-next-layout)
Some users like to be able to quickly switch to the last active workspace. Since EXWM does not come with a built in way to do this, the following hack provides the function exwm-workspace-switch-to-last
.
(defvar exwm-workspace--switch-history-hack (cons exwm-workspace-current-index '()))
(add-hook 'exwm-workspace-switch-hook
(lambda ()
(setq exwm-workspace--switch-history-hack
(cons exwm-workspace-current-index
(car exwm-workspace--switch-history-hack)))))
(defun exwm-workspace-switch-to-last ()
(interactive)
"Switch to the workspace that was used before current workspace"
(exwm-workspace-switch (cdr exwm-workspace--switch-history-hack)))
In order to extend navigation with directions similar to the built-in windmove and since workspaces are regular emacs frames, framemove is known to work well with EXWM. In order to use the same keybindings as windmove use the following:
(setq framemove-hook-into-windmove t)
Too many workspaces: When there are too many workspaces (i.e. more than one per monitor), position of frames with respect to one another can become unpredictable and framemove will tend to switch to unactive workspaces.
windmove and framemove: the packages better together when windmove-wrap-around
is set to nil
.
Some WMs like stumpWM provide a useful feature often called run-or-raise
which launches a program when no instance exists, or raises its existing window into focus. The following snippet goes one step further by burying the buffer if the selected window corresponds to the related program.
(defun exwm-async-run (name)
"Run a process asynchronously"
(interactive)
(start-process name nil name))
(defun run-or-raise-or-dismiss (program program-buffer-name)
"If no instance of the program is running, launch the program.
If an instance already exists, and its corresponding buffer is
displayed on the screen, move to the buffer. If the buffer is not
visible, switch to the buffer in the current window. Finally, if
the current buffer is already that of the program, bury the
buffer (=minimizing in other WM/DE)"
;; check current buffer
(if (string= (buffer-name) program-buffer-name)
(bury-buffer)
;; either switch to or launch program
(progn
(if (get-buffer program-buffer-name)
(progn
(if (get-buffer-window program-buffer-name)
(select-window (display-buffer program-buffer-name) nil)
(exwm-workspace-switch-to-buffer program-buffer-name)))
;; start program
(exwm-async-run program)))))
Example: bind this function to call firefox through s-f
. First press of the keybinding will start firefox. Second keypress will bury the buffer. The last keypress will bring firefox back into focus.
Emacs window dedication is a very useful facility to prevent Emacs from considering a window for buffer display (see (set-window-dedicated-p)
).
It can be used to give exwm
X windows special status and avoid them being replaced by other windows (e.g. compilation or shell command windows).
This makes s-d
toggle dedication on a window, or with a prefix arg, set strong dedication, whereby even user-initiated switching of the buffer is blocked:
(defun my/set-window-dedicated (arg)
"Toggle loose window dedication. If prefix ARG, set strong."
(interactive "P")
(let* ((dedicated (if arg t (if (window-dedicated-p) nil "loose"))))
(message "setting window dedication to %s" dedicated)
(set-window-dedicated-p (selected-window) dedicated)))
(exwm-input-set-key (kbd "s-d") #'my/set-window-dedicated)
You may wish to automatically set all X windows to loosely dedicated:
(defun my/dedicate-exwm-window (&rest ignored)
"Loosely dedicate current window."
(when exwm-class-name
(set-window-dedicated-p (selected-window) "loose")))
(advice-add 'exwm-manage--on-MapNotify :after 'my/dedicate-exwm-window)
(defun my/undedicate-exwm-window (&rest ignored)
"Remove dedication on an exwm window."
(when exwm-class-name
(set-window-dedicated-p (selected-window) nil)))
;; undedicate on unmap, otherwise Emacs window will be destroyed
(advice-add 'exwm-manage--on-UnmapNotify :before 'my/undedicate-exwm-window)
However, this is not fool-proof, and will not stop Emacs from splitting windows whenever it sees fit. Another approach below is to prevent exwm windows from being considered.
One approach for this is to prevent the display-buffer
from considering exwm windows.
The following modifies the list returned by window-list-1
during used by most placement functions, but only during display-buffer
calls. It will exclude exwm
(X) windows from consideration, unless all windows in the frame are exwm
. It uses -reject
from dash.el
.
(defun my/window-exwm-p (window)
"Return t if WINDOW is exwm-mode"
(equal 'exwm-mode (buffer-local-value 'major-mode (window-buffer window))))
;; discourage the use of exwm-windows by `display-buffer'
(defun my/window-list-filter-advice (result)
"Advice fn to exclude exwm windows from returned list, unless all are exwm."
(or (-reject 'my/window-exwm-p result) result))
(defun my/display-buffer-around-advice (orig-fun buffer-or-name
&optional action frame)
"Advice for `display-buffer' to only use non-exwm windows if possible."
(advice-add #'window-list-1 :filter-return #'my/window-list-filter-advice)
(unwind-protect
(apply orig-fun buffer-or-name action frame)
(advice-remove #'window-list-1 #'my/window-list-filter-advice)))
(advice-add #'display-buffer :around #'my/display-buffer-around-advice)
If you want to send keys to a different application - say, a reload event to your browser - you can use xdotool
to do so. It needs an X window ID to be able to send to an application that's not currently focused, so we need to provide that.
(defun my/page-reload ()
"Send a reload event to Firefox."
(interactive)
(let ((key "F5")
(window-id (with-current-buffer (get-buffer "firefox")
(exwm--buffer->id (current-buffer)))))
(shell-command-to-string
(format "xdotool key --window %s %s" window-id key))))
See the manpage for the full list of xdotool
capabilities.