s. So I Write / shelper
← All writing
May 13, 2022 · 6 MIN READ

Emacs in WSL2

By [shelper] · Edit post ↗

On this page

Using Emacs on windows is always an imperfect experience, luckly windows10 comes with WSL2 now, which lets you install Emacs in WSL2 and use it on windows now. I did some hacking to make the experience little bit better, which includes:

  1. open any file on windows in emacs@wsl2
  2. setup org-capture on chrome on windows and capture notes using emacs@wsl2
  3. open links in org-mode in emacs@wsl2 using windows default apps. e.g., open https links using chrome on windows, open document links in MS-office on windows, etc.
  4. copy paste sharing between windows and wsl2
  5. fix for some keyboard shortcuts that’s been screened by windows by default
  6. setup outlook to create outlook links(uuid based) for emails/meetings/contacts in outlook, then open links in org-mode using outlook
  7. insert files into emacs@wsl2

lets go into the steps for each of the above fixes

precondition

On windows 10 you can install GWSL to enable emacs@wsl2 with gui, or you can setup your own VcXsrv so you can open emacs@wsl2 with gui.

open files on windows using emacs@wsl2

in order to add a context menu to open windows files in WSL2, i created a python file with content below:

import sys
import subprocess
import os
import re

import win32gui


class WindowMgr:
    """Encapsulates some calls to the winapi for window management"""

    def __init__(self):
        """Constructor"""
        self._handle = None

    def find_window(self, class_name, window_name=None):
        """find a window by its class_name"""
        self._handle = win32gui.FindWindow(class_name, window_name)

    def _window_enum_callback(self, hwnd, wildcard):
        """Pass to win32gui.EnumWindows() to check all the opened windows"""
        if re.match(wildcard, str(win32gui.GetWindowText(hwnd))) is not None:
            self._handle = hwnd

    def find_window_wildcard(self, wildcard):
        """find a window whose title matches the wildcard regex"""
        self._handle = None
        win32gui.EnumWindows(self._window_enum_callback, wildcard)

    def set_foreground(self):
        """put the window in the foreground"""
        win32gui.SetForegroundWindow(self._handle)


if __name__ == "__main__":

    file_name = (
        '"' + sys.argv[1] + '"'
        if sys.argv[1].startswith("org-protocol")
        else os.path.abspath(sys.argv[1]).replace("\\", "/").replace("C:", "/mnt/c")
    )

    wsl_path_prefix = "//wsl$/Ubuntu/"
    if file_name.startswith(wsl_path_prefix):
        file_name = file_name.replace(wsl_path_prefix, "/")

    command = " ".join(
        [
            "C:/Windows/System32/wsl.exe",
            "-d",
            "Ubuntu",
            "emacsclient",
            "-n",
            file_name,
        ]
    )
    subprocess.Popen(command, shell=True)

    w = WindowMgr()
    w.find_window_wildcard(".*Doom Emac")
    w.set_foreground()

This works fine on windows as long you have pythonw.exe and you have the icon file specified above exists if you use python.exe instead of pythonw.exe, it still works but will always generate a cmd window when you call emacs@wsl2.

setup org capture for emacs@wsl2

if you already have the above script wsl_emacs.py in place, then it is relatively simple, just run the reg file below

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\org-protocol]
@="URL:Org Protocol"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\org-protocol\shell]

[HKEY_CLASSES_ROOT\org-protocol\shell\open]

[HKEY_CLASSES_ROOT\org-protocol\shell\open\command]
@="\"C:\\Users\\username\\scoop\\apps\\python\\current\\pythonw.exe\" \"C:\\Path\\to\\wsl_emacs.py\" \"%1\""

of coures you need to install the org-capture extension, and add your own capture template for org-capture, see more details at here

this is done by installing the wslu on your wsl2, and then you can config your org-mode by:

;; "Set org-file-apps based on the system type"
(when IS-WSL
  (setq org-file-apps '((remote . emacs)
                        (auto-mode . emacs)
                        (directory . emacs)
                        ("\\.mm\\'" . "wslview \"%s\"")
                        ("\\.x?html?\\'" . "wslview \"%s\"")
                        ("\\.pptx?\\'" . "wslview \"%s\"")
                        ("\\.xlsx?\\'" . "wslview \"%s\"")
                        ("\\.docx?\\'" . "wslview \"%s\"")
                        ("\\.pdf\\'" .  "wslview \"%s\"")))
  ;; need to set explorer for open weblinks and htmls seperately
  (setq browse-url-generic-program "/mnt/c/Program Files \(x86\)/Google/Chrome/Application/chrome.exe")
  )

copy paste sharing between wsl2 and windows host

This can be easily done using GWSL above

fix keyboard shortcut for C-shift-0

I don’t know why but it seems windows blocks this keybind to be used. this is annoying, what i did is i remap this C-shift-0 to something i dont use with powertoys on windows, e.g., i remap C-shift-0 to C-shift-., then i have the following line to set my emacs keybinding

;; Windows 10 blocks Ctrl-Shift-0, So we using powertoy to cheat the system
;; when we press "C-)", it becomes "C->"
(cond (IS-WSL (map! "C->" #'sp-forward-slurp-sexp))
      (t (map! "C-)" #'sp-forward-slurp-sexp)))

Outlook for Org-mode

insert windows files into emacs@wsl2

since emacs runs in wsl, we cannot drag and drop files between windows and emacs@wsl2. so i wrote a simple script to get the path of files in windows, and convert it to path in wsl2

import os
import sys

file_path = os.path.abspath(sys.argv[-1]).replace("\\", "/").replace("C:", "/mnt/c")

wsl_path_prefix = "//wsl$/Ubuntu/"
if file_path.startswith(wsl_path_prefix):
    file_path = file_path.replace(wsl_path_prefix, "/")

command = "echo " + file_path.strip() + "| clip"
os.system(command)

then I create the context menu WSL Path by the registry file

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\Copy WSL path]
@="WSL Path"

[HKEY_CLASSES_ROOT\*\shell\Copy WSL path\Command]
@="\"C:\\Users\\username\\scoop\\apps\\python\\current\\pythonw.exe\" \"C:\\path\\to\\wsl_path.py\" \"%1\""

share clipboard between windows host and WSL2

this is done by using powershell to get clipboard image saved and read the image into xclip for clipboard access in WSL2

(defun win2wsl-clipped-image()
  "use powershell to save the clipped image to wsl and load it to xclip"
  (let* ((powershell "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe")
         (file-name "//wsl$/Ubuntu/home/mpnv38/tmp/clip_win2wsl.png")
         (file-name-wsl "~/tmp/clip_win2wsl.png")
         )
    (shell-command (concat powershell " -noprofile -command \"(Get-Clipboard -Format Image).Save(\\\"" file-name "\\\")\""))
    (call-process-shell-command (concat "xclip -selection clipboard -t image/png -i " file-name-wsl))
    )
  )
(advice-add 'org-download-clipboard :before #'win2wsl-clipped-image)

This is it, let me know in comments if you have any question or suggestion!

emacs
← setup hugo with github pages on githubMigrating to new python version with lots of dependencies →