feat(*): Emacs & i3 integration

This commit is contained in:
Pavel Korytov 2021-10-03 20:46:40 +03:00
parent a0f25dd23f
commit 20af706fa2
7 changed files with 300 additions and 81 deletions

View file

@ -42,6 +42,7 @@
"curl"
"bind:utils"
"polybar"
"python-i3-balance-workspace"
"i3-gaps"
"xinput"
"xgamma"

View file

@ -27,47 +27,48 @@ bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcu
# General settings:1 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:1]]
bindsym $mod+Shift+q kill
bindsym $mod+Shift+q exec emacs-i3-integration kill
# Managing windows:1 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:2]]
bindsym $mod+h focus left
bindsym $mod+j focus down
bindsym $mod+k focus up
bindsym $mod+l focus right
bindsym $mod+h exec emacs-i3-integration focus left
bindsym $mod+j exec emacs-i3-integration focus down
bindsym $mod+k exec emacs-i3-integration focus up
bindsym $mod+l exec emacs-i3-integration focus right
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
bindsym $mod+Left exec emacs-i3-integration focus left
bindsym $mod+Down exec emacs-i3-integration focus down
bindsym $mod+Up exec emacs-i3-integration focus up
bindsym $mod+Right exec emacs-i3-integration focus right
# Managing windows:2 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:3]]
bindsym $mod+Shift+h move left
bindsym $mod+Shift+j move down
bindsym $mod+Shift+k move up
bindsym $mod+Shift+l move right
bindsym $mod+Shift+h exec emacs-i3-integration move left
bindsym $mod+Shift+j exec emacs-i3-integration move down
bindsym $mod+Shift+k exec emacs-i3-integration move up
bindsym $mod+Shift+l exec emacs-i3-integration move right
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
bindsym $mod+Shift+Left exec emacs-i3-integration move left
bindsym $mod+Shift+Down exec emacs-i3-integration move down
bindsym $mod+Shift+Up exec emacs-i3-integration move up
bindsym $mod+Shift+Right exec emacs-i3-integration move right
# Managing windows:3 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:4]]
bindsym $mod+s split h
bindsym $mod+v split v
bindsym $mod+s exec emacs-i3-integration split h
bindsym $mod+v exec emacs-i3-integration split v
# Managing windows:4 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:5]]
# enter fullscreen mode for the focused container
bindsym $mod+f fullscreen toggle
bindsym $mod+c fullscreen toggle global
# Managing windows:5 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:6]]
bindsym $mod+w layout stacking
bindsym $mod+t layout tabbed
bindsym $mod+e layout toggle split
bindsym $mod+e exec emacs-i3-integration layout toggle split
# Managing windows:6 ends here
# [[file:../../Desktop.org::*Managing windows][Managing windows:7]]
@ -226,31 +227,31 @@ bindsym $mod+g mode "inner gaps"
bindsym $mod+Shift+g mode "outer gaps"
# Keybindings:1 ends here
# [[file:../../Desktop.org::*Move & resize windows][Move & resize windows:1]]
# resize window (you can also use the mouse for that)
# [[file:../../Desktop.org::*Move & resize windows][Move & resize windows:2]]
mode "resize" {
# These bindings trigger as soon as you enter the resize mode
bindsym h resize shrink width 10 px or 10 ppt
bindsym j resize grow height 10 px or 10 ppt
bindsym k resize shrink height 10 px or 10 ppt
bindsym l resize grow width 10 px or 10 ppt
bindsym h exec emacs-i3-integration resize shrink width 10 px or 10 ppt
bindsym j exec emacs-i3-integration resize grow height 10 px or 10 ppt
bindsym k exec emacs-i3-integration resize shrink height 10 px or 10 ppt
bindsym l exec emacs-i3-integration resize grow width 10 px or 10 ppt
bindsym Shift+h resize shrink width 100 px or 100 ppt
bindsym Shift+j resize grow height 100 px or 100 ppt
bindsym Shift+k resize shrink height 100 px or 100 ppt
bindsym Shift+l resize grow width 100 px or 100 ppt
bindsym Shift+h exec emacs-i3-integration resize shrink width 100 px or 100 ppt
bindsym Shift+j exec emacs-i3-integration resize grow height 100 px or 100 ppt
bindsym Shift+k exec emacs-i3-integration resize shrink height 100 px or 100 ppt
bindsym Shift+l exec emacs-i3-integration resize grow width 100 px or 100 ppt
# same bindings, but for the arrow keys
bindsym Left resize shrink width 10 px or 10 ppt
bindsym Down resize grow height 10 px or 10 ppt
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
bindsym Left exec emacs-i3-integration resize shrink width 10 px or 10 ppt
bindsym Down exec emacs-i3-integration resize grow height 10 px or 10 ppt
bindsym Up exec emacs-i3-integration resize shrink height 10 px or 10 ppt
bindsym Right exec emacs-i3-integration resize grow width 10 px or 10 ppt
bindsym Shift+Left resize shrink width 100 px or 100 ppt
bindsym Shift+Down resize grow height 100 px or 100 ppt
bindsym Shift+Up resize shrink height 100 px or 100 ppt
bindsym Shift+Right resize grow width 100 px or 100 ppt
bindsym Shift+Left exec emacs-i3-integration resize shrink width 100 px or 100 ppt
bindsym Shift+Down exec emacs-i3-integration resize grow height 100 px or 100 ppt
bindsym Shift+Up exec emacs-i3-integration resize shrink height 100 px or 100 ppt
bindsym Shift+Right exec emacs-i3-integration resize grow width 100 px or 100 ppt
bindsym equal exec i3-emacs-balance-windows
# back to normal: Enter or Escape
bindsym Return mode "default"
@ -278,7 +279,7 @@ mode "move" {
}
bindsym $mod+m mode "move" focus floating
# Move & resize windows:1 ends here
# Move & resize windows:2 ends here
# [[file:../../Desktop.org::*Integration with rofi][Integration with rofi:1]]
bindsym $mod+d exec "rofi -modi 'drun,run' -show drun"

View file

@ -211,7 +211,8 @@
comint
git-timemachine
magit
prodigy)))
prodigy
slime)))
(defun minibuffer-keyboard-quit ()
"Abort recursive edit.
@ -303,6 +304,7 @@ then it takes a second \\[keyboard-quit] to abort the minibuffer."
"h" 'previous-buffer
"k" 'kill-buffer
"b" 'persp-ivy-switch-buffer
"r" 'revert-buffer
"u" 'ibuffer)
(general-nmap
@ -335,6 +337,72 @@ then it takes a second \\[keyboard-quit] to abort the minibuffer."
(global-set-key (kbd "C-+") 'my/zoom-in)
(global-set-key (kbd "C-=") 'my/zoom-out)
(defmacro i3-msg (&rest args)
`(start-process "emacs-i3-windmove" nil "i3-msg" ,@args))
(add-hook 'after-init-hook #'server-start)
(defun my/emacs-i3-windmove (dir)
(let ((other-window (windmove-find-other-window dir)))
(if (or (null other-window) (window-minibuffer-p other-window))
(i3-msg "focus" (symbol-name dir))
(windmove-do-window-select dir))))
(defun my/emacs-i3-move-window (dir)
(let ((other-window (windmove-find-other-window dir)))
(if (null other-window)
(i3-msg "move" (symbol-name dir))
(window-swap-states (selected-window) other-window))))
(defun my/emacs-i3-resize-direction-exists-p (dir)
(some #'windmove-find-other-window
(pcase dir
('width '(left right))
('height '(up down)))))
(defun my/emacs-i3-resize-window (dir kind value)
(if (or (one-window-p)
(not (my/emacs-i3-resize-direction-exists-p dir)))
(i3-msg "resize" (symbol-name kind) (symbol-name dir)
(format "%s px or %s ppt" value value))
(setq value (/ value 2))
(pcase kind
('shrink
(pcase dir
('width
(evil-window-decrease-width value))
('height
(evil-window-decrease-height value))))
('grow
(pcase dir
('width
(evil-window-increase-width value))
('height
(evil-window-increase-height value)))))))
(use-package transpose-frame
:straight t
:commands (transpose-frame))
(defun my/emacs-i3-integration (command)
(pcase command
((rx bos "focus")
(my/emacs-i3-windmove
(intern (elt (split-string command) 1))))
((rx bos "move")
(my/emacs-i3-move-window
(intern (elt (split-string command) 1))))
((rx bos "resize")
(my/emacs-i3-resize-window
(intern (elt (split-string command) 2))
(intern (elt (split-string command) 1))
(string-to-number (elt (split-string command) 3))))
("layout toggle split" (transpose-frame))
("split h" (evil-window-split))
("split v" (evil-window-vsplit))
("kill" (evil-quit))
(- (i3-msg command))))
(use-package visual-fill-column
:straight t
:config
@ -2851,6 +2919,12 @@ then it takes a second \\[keyboard-quit] to abort the minibuffer."
;; (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode)
(add-hook 'emacs-lisp-mode-hook #'lispy-mode)
(use-package slime
:straight t
:config
(setq inferior-lisp-program "sbcl")
(add-hook 'slime-repl-mode 'smartparens-mode))
(add-hook 'lisp-mode-hook #'aggressive-indent-mode)
;; (add-hook 'emacs-lisp-mode-hook #'smartparens-strict-mode)
(add-hook 'lisp-mode-hook #'lispy-mode)

View file

@ -283,54 +283,57 @@ bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcu
** Managing windows
Some keybindings for managing windows.
=emacs-i3-integration= is a script to pass some command to Emacs to get a consistent set of keybindings in both i3 and Emacs. Check out [[file:Emacs.org::i3 integration][the section in Emacs.org]] for details.
Kill focused windows
#+begin_src conf-space
bindsym $mod+Shift+q kill
bindsym $mod+Shift+q exec emacs-i3-integration kill
#+end_src
Change focus
#+begin_src conf-space
bindsym $mod+h focus left
bindsym $mod+j focus down
bindsym $mod+k focus up
bindsym $mod+l focus right
bindsym $mod+h exec emacs-i3-integration focus left
bindsym $mod+j exec emacs-i3-integration focus down
bindsym $mod+k exec emacs-i3-integration focus up
bindsym $mod+l exec emacs-i3-integration focus right
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
bindsym $mod+Left exec emacs-i3-integration focus left
bindsym $mod+Down exec emacs-i3-integration focus down
bindsym $mod+Up exec emacs-i3-integration focus up
bindsym $mod+Right exec emacs-i3-integration focus right
#+end_src
Move windows around
#+begin_src conf-space
bindsym $mod+Shift+h move left
bindsym $mod+Shift+j move down
bindsym $mod+Shift+k move up
bindsym $mod+Shift+l move right
bindsym $mod+Shift+h exec emacs-i3-integration move left
bindsym $mod+Shift+j exec emacs-i3-integration move down
bindsym $mod+Shift+k exec emacs-i3-integration move up
bindsym $mod+Shift+l exec emacs-i3-integration move right
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
bindsym $mod+Shift+Left exec emacs-i3-integration move left
bindsym $mod+Shift+Down exec emacs-i3-integration move down
bindsym $mod+Shift+Up exec emacs-i3-integration move up
bindsym $mod+Shift+Right exec emacs-i3-integration move right
#+end_src
Split windows
#+begin_src conf-space
bindsym $mod+s split h
bindsym $mod+v split v
bindsym $mod+s exec emacs-i3-integration split h
bindsym $mod+v exec emacs-i3-integration split v
#+end_src
Enter fullscreen mode
#+begin_src conf-space
# enter fullscreen mode for the focused container
bindsym $mod+f fullscreen toggle
bindsym $mod+c fullscreen toggle global
#+end_src
Changing layout
#+begin_src conf-space
bindsym $mod+w layout stacking
bindsym $mod+t layout tabbed
bindsym $mod+e layout toggle split
bindsym $mod+e exec emacs-i3-integration layout toggle split
#+end_src
Toggle tiling/floating, switch between tiled and floating windows
@ -518,34 +521,46 @@ bindsym $mod+g mode "inner gaps"
bindsym $mod+Shift+g mode "outer gaps"
#+end_src
** Move & resize windows
A more or less standard set of keybindings to move & resize floating windows.
| Guix dependency |
|-----------------------------|
| python-i3-balance-workspace |
A more or less standard set of keybindings to move & resize floating windows. Just be careful to always make a way to return from these new modes, otherwise you'd end up in a rather precarious situation.
[[https://github.com/atreyasha/i3-balance-workspace][i3-balance-workspace]] is a small Python package to balance the i3 windows, but for the Emacs integration I also want this button to balance the Emacs windows, so here is a small script to do just that.
#+begin_src bash :tangle ~/bin/scripts/i3-emacs-balance-windows
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
emacsclient -e "(balance-windows)" &
fi
i3_balance_workspace
#+end_src
Just be careful to always make a way to return from these new modes, otherwise you'd end up in a rather precarious situation.
#+begin_src conf-space
# resize window (you can also use the mouse for that)
mode "resize" {
# These bindings trigger as soon as you enter the resize mode
bindsym h resize shrink width 10 px or 10 ppt
bindsym j resize grow height 10 px or 10 ppt
bindsym k resize shrink height 10 px or 10 ppt
bindsym l resize grow width 10 px or 10 ppt
bindsym h exec emacs-i3-integration resize shrink width 10 px or 10 ppt
bindsym j exec emacs-i3-integration resize grow height 10 px or 10 ppt
bindsym k exec emacs-i3-integration resize shrink height 10 px or 10 ppt
bindsym l exec emacs-i3-integration resize grow width 10 px or 10 ppt
bindsym Shift+h resize shrink width 100 px or 100 ppt
bindsym Shift+j resize grow height 100 px or 100 ppt
bindsym Shift+k resize shrink height 100 px or 100 ppt
bindsym Shift+l resize grow width 100 px or 100 ppt
bindsym Shift+h exec emacs-i3-integration resize shrink width 100 px or 100 ppt
bindsym Shift+j exec emacs-i3-integration resize grow height 100 px or 100 ppt
bindsym Shift+k exec emacs-i3-integration resize shrink height 100 px or 100 ppt
bindsym Shift+l exec emacs-i3-integration resize grow width 100 px or 100 ppt
# same bindings, but for the arrow keys
bindsym Left resize shrink width 10 px or 10 ppt
bindsym Down resize grow height 10 px or 10 ppt
bindsym Up resize shrink height 10 px or 10 ppt
bindsym Right resize grow width 10 px or 10 ppt
bindsym Left exec emacs-i3-integration resize shrink width 10 px or 10 ppt
bindsym Down exec emacs-i3-integration resize grow height 10 px or 10 ppt
bindsym Up exec emacs-i3-integration resize shrink height 10 px or 10 ppt
bindsym Right exec emacs-i3-integration resize grow width 10 px or 10 ppt
bindsym Shift+Left resize shrink width 100 px or 100 ppt
bindsym Shift+Down resize grow height 100 px or 100 ppt
bindsym Shift+Up resize shrink height 100 px or 100 ppt
bindsym Shift+Right resize grow width 100 px or 100 ppt
bindsym Shift+Left exec emacs-i3-integration resize shrink width 100 px or 100 ppt
bindsym Shift+Down exec emacs-i3-integration resize grow height 100 px or 100 ppt
bindsym Shift+Up exec emacs-i3-integration resize shrink height 100 px or 100 ppt
bindsym Shift+Right exec emacs-i3-integration resize grow width 100 px or 100 ppt
bindsym equal exec i3-emacs-balance-windows
# back to normal: Enter or Escape
bindsym Return mode "default"

112
Emacs.org
View file

@ -817,6 +817,118 @@ Evil does a pretty good job of uniting these two in the set of vim-like keybindi
(global-set-key (kbd "C-+") 'my/zoom-in)
(global-set-key (kbd "C-=") 'my/zoom-out)
#+end_src
** i3 integration
I'm a bit jealous of EXWM having a consistent set of keybindings to manage both Emacs windows and X windows, so I figured I could try to implement something like this with i3.
The basic idea is to launch a normal i3 command with =i3-msg= in case the current window is not Emacs, otherwise pass that command to Emacs with =emacsclient=. In Emacs, execute the command if possible, otherwise pass the command back to i3.
I actually tried to use EXWM but returned back to i3 because EXWM wasn't snappy enough. While this integration adds some overhead, I can't detect it even in the worst case (i3 -> Emacs -> i3), so the integration feels seamless.
At any rate, we need a script to do the i3 -> Emacs part:
#+begin_src bash :tangle ~/bin/scripts/emacs-i3-integration
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
command="(my/emacs-i3-integration \"$@\")"
emacsclient -e "$command"
else
i3-msg $@
fi
#+end_src
This script is being run from the [[file:Desktop.org::*i3wm][i3 configuration]].
For the second part, we need to call i3 from emacs. Here is a simple macro to do just that:
#+begin_src emacs-lisp
(defmacro i3-msg (&rest args)
`(start-process "emacs-i3-windmove" nil "i3-msg" ,@args))
#+end_src
Also, we have to make sure that Emacs starts a server, so here is a hook to do just that:
#+begin_src emacs-lisp
(add-hook 'after-init-hook #'server-start)
#+end_src
Then we have to handle the required i3 commands. First, for the =focus= command I want to move to the Emacs window in the given direction if there is one, otherwise move to the X window in the same direction. Fortunately, i3 and windmove have the same names for directions, so the function is rather straightforward.
Also, one caveat here is that the minibuffer is always below, so it is necessary to check for that as well.
#+begin_src emacs-lisp
(defun my/emacs-i3-windmove (dir)
(let ((other-window (windmove-find-other-window dir)))
(if (or (null other-window) (window-minibuffer-p other-window))
(i3-msg "focus" (symbol-name dir))
(windmove-do-window-select dir))))
#+end_src
For the =move= command I want to move the Emacs window in the required direction if there is space in there, otherwise move an X window, which will be the Emacs frame.
I tried to use =evil-move-window= here, but it doesn't behave quite like i3, for instance, =(evil-move-window 'right)= in a tree-column split would move the window from the far left side to the far right side (bypassing center). =window-swap-states= with a window in the given direction does better.
#+begin_src emacs-lisp
(defun my/emacs-i3-move-window (dir)
(let ((other-window (windmove-find-other-window dir)))
(if (null other-window)
(i3-msg "move" (symbol-name dir))
(window-swap-states (selected-window) other-window))))
#+end_src
Next, =resize grow= and =resize shrink=. To avoid running resize when there is no place to resize to, here is a simple predicate which checks whether there is space in the given direction.
#+begin_src emacs-lisp
(defun my/emacs-i3-resize-direction-exists-p (dir)
(some #'windmove-find-other-window
(pcase dir
('width '(left right))
('height '(up down)))))
#+end_src
Now we can use that to perform the actual resizing. =evil-window-= functions do that nicely.
#+begin_src emacs-lisp
(defun my/emacs-i3-resize-window (dir kind value)
(if (or (one-window-p)
(not (my/emacs-i3-resize-direction-exists-p dir)))
(i3-msg "resize" (symbol-name kind) (symbol-name dir)
(format "%s px or %s ppt" value value))
(setq value (/ value 2))
(pcase kind
('shrink
(pcase dir
('width
(evil-window-decrease-width value))
('height
(evil-window-decrease-height value))))
('grow
(pcase dir
('width
(evil-window-increase-width value))
('height
(evil-window-increase-height value)))))))
#+end_src
[[https://github.com/emacsorphanage/transpose-frame][transpose-frame]] is a package to "transpose" the current frame layout, which behaves someone similar to the =layout toggle split= command in i3.
#+begin_src emacs-lisp
(use-package transpose-frame
:straight t
:commands (transpose-frame))
#+end_src
Finally, an entrypoint for the Emacs integration. In addition to the commands defined above, it processes =split= and =kill= commands and passes every other command back to i3.
#+begin_src emacs-lisp
(defun my/emacs-i3-integration (command)
(pcase command
((rx bos "focus")
(my/emacs-i3-windmove
(intern (elt (split-string command) 1))))
((rx bos "move")
(my/emacs-i3-move-window
(intern (elt (split-string command) 1))))
((rx bos "resize")
(my/emacs-i3-resize-window
(intern (elt (split-string command) 2))
(intern (elt (split-string command) 1))
(string-to-number (elt (split-string command) 3))))
("layout toggle split" (transpose-frame))
("split h" (evil-window-split))
("split v" (evil-window-vsplit))
("kill" (evil-quit))
(- (i3-msg command))))
#+end_src
** Editing helpers
*** Visual fill column mode
#+begin_src emacs-lisp

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# [[file:../../Emacs.org::*i3 integration][i3 integration:1]]
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
command="(my/emacs-i3-integration \"$@\")"
emacsclient -e "$command"
else
i3-msg $@
fi
# i3 integration:1 ends here

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# [[file:../../Desktop.org::*Move & resize windows][Move & resize windows:1]]
if [[ $(xdotool getactivewindow getwindowname) =~ ^emacs(:.*)?@.* ]]; then
emacsclient -e "(balance-windows)" &
fi
i3_balance_workspace
# Move & resize windows:1 ends here