Look, listen. Any of my friends are gonna think I'm a terrible goblin when I say, and everyone else will be so so confused, but: I spent some time setting up Emacs on my SteamDeck so that very narrow parts of my org-mode's functionality works with the game pad inputs.
Wow a handheld Linux device from a Big Company!! ATTACH
The SteamDeck is Valve's first attempt at a handheld game console. It's basically a really chonky Nintendo Switch which does not switch, and runs an operating system you may have heard of called Arch Linux.
Now, the default experience is much closer to Steam's "big picture" mode – it's called Deck Mode or Gaming Mode and it's a nice UI built around the controller and touch screen that lets you launch many of the games in your Steam library.
You can peel back a few layers, though, and unlock some neat functionality like, well, emulating almost every system up to the PS3 and Switch. You can run arbitrary Windows applications through Valve's Proton fork of WINE. You can jump to a full KDE Plasma desktop and run any application available on FlatHub.
Me? I installed Nix and Emacs on it
Okay, so I did all of that, too, actually and have a really cool little gaming machine that has, well, nearly every game I have ever wanted to play on it. Things which need a lot of screen real estate or keyboard don't work obviously, so games like Cogmind and Sid Meier's Alpha Centauri are out, but I can comfortably run both of those on my laptop.
But I was missing something… I was missing Emacs! Now, look here, kids. Emacs isn't just a fancy programming tool for old farts and hipsters. It also is a text editor! And it's the only gosh darn text editor that works for the weird outlining format I have built my life around, ye olde org-mode. Without a keyboard, this is not so useful in general, but there are weird little Hypermedia apps on top of it like the one I use for Spaced Repetition Study – i can annotate any of my notes and call up flashcards on them, and it only needs like 10 buttons. I would like to do those flash cards without all my laptop and chat windows and distractions, and this means the Steam Deck could be perfect for this. It sure beats trying to extract Anki decks from my notes. I really do recommend using org-fc to stop forgetting things in your notes.
My existing Emacs is a version referred to as GccEmacs – it generates C code for Emacs Lisp libraries and then uses a GCC just in time compiler to generate native byte code which is noticeably faster than previous versions. I use nix-community/emacs-overlay to enable this, a set of helper functions that lay on top of nixpkgs's Emacs package infrastructure to provide pre-compiling of libraries, dependency management, etc.
use steamdeck-nix-multiuser to install Nix
This is a neat little set of scripts that an acquaintance of mine from Fediverse is working on. There is a script in the repository which sets up overlay file systems and bind-mounts to have a persistent installation of Nix which will survive Valve's OS updates. Check out the repository, but also here's what I did to get all the way to a working home-manager installation.
visudo -f /etc/sudoers.d/wheel
%wheel ALL=(ALL) NOPASSWD: ALL
usermod -aG wheel deck
./steamdeck-nix-multiuser/scripts/nixstrap
sh <(curl -L https://nixos.org/nix/install) --daemon
nix-channel --add https://nixos.org/channels/nixos-22.05 nixpkgs
nix-channel --add https://github.com/nix-community/home-manager/archive/release-22.05.tar.gz home-manager
nix-channel --update
add to .bash_profile
:
. ~/.nix-profile/etc/profile.d/nix.sh
export NIX_PATH=$HOME/.nix-defexpr/channels:/nix/var/nix/profiles/per-user/root/channels${NIX_PATH:+:$NIX_PATH}
log out and back in, then:
nix-shell '<home-manager>' -A install
In theory I could get cached GccEmacs builds, but in practice it builds from source some times:
nix-env -iA cachix -f https://cachix.org/api/v1/install
cachix use nix-community
From Home Manager to Running Emacs
So now that I have home-manager
in my
path, I can make a Nix file repository or clone my existing Arroyo Home Manager stuff on to it.
For simplicity's sake, I made a new one:
{ config, pkgs, lib, ... }:
let
my-emacs = import ./pkgs/emacs.nix { inherit pkgs; };
in
{
programs.home-manager.enable = true;
home.username = "deck";
home.homeDirectory = "/home/deck";
fonts.fontconfig.enable = true;
This is important to have the applications' .desktop
files show up in the launcher:
[
xdg.systemDirs.data = "/home/deck/.nix-profile/share/"
];
I installed Syncthing and some other simple tools which org-fc needs:
with pkgs; [
home.packages =
ipaexfont# python3Packages.jisho-api
syncthingtray
sqlite
gawk
findutils
mpv
my-emacs
pastebinit];
# syncthing
true;
services.syncthing.enable = true;
services.syncthing.tray.enable = "${pkgs.syncthingtray}/bin/syncthingtray --wait";
systemd.user.services.syncthingtray.Service.ExecStart = lib.mkForce ["graphical-session-pre.target"];
systemd.user.services.syncthingtray.Unit.After = lib.mkForce []; systemd.user.services.syncthingtray.Unit.Requires = lib.mkForce
and of course my init.el
and emacs-overlay
itself:
".emacs.d/init.el".source = ./init.el;
home.file.
# emacs-overlay
[
nixpkgs.overlays = (import (builtins.fetchTarball {
url = https://github.com/nix-community/emacs-overlay/archive/master.tar.gz;
}))
];
}
My Emacs package embeds a build of org-fc
in it since it's not packaged in an
ELPA:
{ pkgs }:
{
pkgs.emacsWithPackagesFromUsePackage config = ../init.el;
package = pkgs.emacsPgtkNativeComp;
alwaysEnsure = true;
override = epkgs: epkgs // {
org-fc = epkgs.melpaBuild {
pname = "org-fc";
version = "20220823.2107";
commit = "f64b5336485a42be91cfe77850c02a41575f5984";
src = pkgs.fetchFromGitHub {
owner = "l3kn";
repo = "org-fc";
rev = "f64b5336485a42be91cfe77850c02a41575f5984";
sha256 = "sha256-pb1UWhqim88uWVj/8UYEEGTT3HL3sespo3f+BPIeCrQ=";
};
recipe = pkgs.writeText "recipe" ''
(org-fc
:files (:defaults "awk")
:repo "l3kn/org-fc"
:fetcher github)
'';
packageRequires = [ pkgs.gawk epkgs.hydra ];
meta = with pkgs; {
homepage = "https://www.leonrische.me/fc/index.html";
license = lib.licenses.gpl3Plus;
};
};
};
}
My init.el
is verbose, I'll only
include some relevant parts of it below:
Relevant init.el
functionality
Start by setting up use-package:
(package-initialize)'package-archives
(add-to-list "gnu" . "http://elpa.gnu.org/packages/") t)
'('package-archives
(add-to-list "melpa" . "http://melpa.org/packages/") t)
'(
setq cce/did-refresh-packages nil)
(defun install-pkg (pkg)
(unless (package-installed-p pkg)
(unless cce/did-refresh-packages
(
(package-refresh-contents)setq cce/did-refresh-packages t))
(
(package-install pkg)))
'use-package)
(install-pkg
require 'use-package)
(setq use-package-always-ensure t
(t) use-package-compute-statistics
Minimal-ish org-mode configuration for this:
use-package org-bullets
(
:after org
:hook (org-mode . org-bullets-mode))use-package org-indent
(
:after orgnil
:ensure
:diminish
:configsetq org-startup-indented t
(nil)
org-hide-leading-stars
:hook (org-mode . org-indent-mode))
setq org-export-coding-system 'utf-8)
('utf-8)
(prefer-coding-system 'unicode)
(set-charset-priority setq default-process-coding-system '(utf-8-unix . utf-8-unix))
(
use-package org-id
(
:after orgnil
:ensure
:configsetq org-id-method 'ts)
(setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
(setq org-clone-delete-id t))
(
use-package org
(
:demand
:bind"C-c l" . org-store-link)
("C-c c" . org-capture)
(
:configrequire 'org-compat)
(setq org-startup-folded t
('error
org-catch-invisible-edits t
org-src-fontify-natively t
org-return-follows-link 0)
org-edit-src-content-indentation ;; use org-mode in org files
'auto-mode-alist
(add-to-list "\\.\\(org\\|org_archive\\)$" . org-mode))
'(;; org-modules defines a bunch of "plugins" to load
setq org-modules '(org-id))
(setq org-file-apps '((auto-mode . emacs)
(t . system)))
(setq org-image-actual-width nil)) (
I use Evil Mode, but these keybindings
basically map to the defaults; be sure to uncomment the (evil
prefixed lines if you do, too.
use-package org-fc
(
:custom"~/org/"))
(org-fc-directories '("~/org/org-fc-reviews.tsv"))
(org-fc-review-history-file (expand-file-name
:configsetq org-fc-custom-contexts
(and (tag "japanese")
'((japanese . (:filter ("vocabulary"))))
(tag "Buddhism")))
(buddhism . (:filter (tag "trivia")))
(trivia . (:filter (tag or (tag "jokugo") (tag "kanji"))))
(kanji . (:filter ("poem")))
(poetry . (:filter (tag "tokipona")))
(tokipona . (:filter (tag not (tag "vocabulary"))))))
(row . (:filter (require 'org-fc-hydra)
('srs #'org-fc-dashboard)
(defalias ;; (evil-set-initial-state #'org-fc-dashboard-mode 'motion)
;; (evil-define-minor-mode-key '(normal insert emacs) 'org-fc-review-flip-mode
;; (kbd "RET") 'org-fc-review-flip
;; (kbd "n") 'org-fc-review-flip
;; (kbd "s") 'org-fc-review-suspend-card
;; (kbd "q") 'org-fc-review-quit)
;; (evil-define-minor-mode-key '(normal insert emacs) 'org-fc-review-rate-mode
;; (kbd "a") 'org-fc-review-rate-again
;; (kbd "h") 'org-fc-review-rate-hard
;; (kbd "g") 'org-fc-review-rate-good
;; (kbd "e") 'org-fc-review-rate-easy
;; (kbd "s") 'org-fc-review-suspend-card
;; (kbd "q") 'org-fc-review-quit)
)
Make it look nice and legible:
use-package ef-themes
(t
:ensure
:demand
:config'ef-autumn t)
(load-theme 'ef-spring t t))
(load-theme
defun cce/set-font-scale (size)
("nWhat font size do you want? ")
(interactive require 'cl)
('mode-line nil :inherit 'fixed-pitch :height (+ 10 size))
(set-face-attribute
(lexical-let ((size size))'org (set-face-attribute 'org-block nil :inherit 'fixed-pitch))
(with-eval-after-load 'linum (set-face-attribute 'linum nil :inherit 'default :height size)))
(with-eval-after-load 'default nil :height size)
(set-face-attribute 'fixed-pitch nil :inherit 'default)
(set-face-attribute 'variable-pitch nil :slant 'oblique :height size))
(set-face-attribute 140) (cce/set-font-scale
Some helpers from my Japanese Study configuration:
'org
(with-eval-after-load defun cce/eldoc-jhk (&rest args)
(when (looking-at "[ぁ-ヿ]")
('char)) 'name)))
(get-char-code-property (string-to-char (thing-at-point
defun cce/eldoc-jhk-setup ()
('eldoc-documentation-functions
(add-hook #'cce/eldoc-jhk nil t))
'org-mode-hook #'cce/eldoc-jhk-setup))
(add-hook
'jisho-this 'cce/jisho-at-point)
(defalias
defun cce/jisho-at-point ()
(
(interactive)let ((chr (thing-at-point 'word)))
(format "https://jisho.org/search/%s" chr)))) (browse-url (
These keybindings will come in handy when setting up the gamepad mapping:
"<f7>") #'execute-extended-command)
(global-set-key (kbd "<f9>") #'execute-extended-command-for-buffer)
(global-set-key (kbd "<f8>") #'srs)
(global-set-key (kbd "<f12>") #'org-fc-review) (global-set-key (kbd
Briefly: x11docker
So now that Emacs is running Desktop Mode, things are moving along nicely, but we're still falling short: Desktop Mode does not have access to all the controller rebinding stuff that is present in Gaming Mode. They're "just" gamepad devices as far as linux is concerned and Emacs doesn't support gamepad devices, so you'd have to make a goofy little cyberdeck for this to be usable. This is, of course, a shortcoming which would/should/could be rectified with enough parentheses but there are other options.
Viv pointed me to this gnarly fucking shell script called x11docker and it does
seem useful in general. Ultimately it can be used to run a full desktop
inside of the Gaming Mode interface. It seems like a good tool to have
in the belt in general. In theory i could take the x11docker/xfce
image or so and extend it with
nixpkgs
's dockerTools
to inject my emacs-overlay package
or even a full home-manager derivation but augh augh augh. i spent a few
hours on this and got frustrated with it.
Emacs inside of Gaming Mode
So instead, I found this
neat little hack to run Desktop Mode within Gaming
Mode. You end up with a little desktop-in-gaming.sh
inside of your Steam Deck
launcher, and when you click it the desktop mode opens. I have no idea
what happens if you launch Steam in there! don't do it! Just launch
Emacs.
#!/bin/sh
unset LD_PRELOAD
exec startplasma-wayland --xwayland --x11-display $DISPLAY --no-lockscreen --width 1280 --height 800 -- plasma_session
Set Emacs to Auto Start in the KDE settings, you
could even have Home Manager set all this up for you. I also recommend
making a Window Management Rule in System Settings to maximize the Emacs
window when it opens, or invoke M-x toggle-frame-fullscreen
. Someone with
slightly more patience than me could probably just get this running with
a home-manager managed Weston or Sway but this is fine enough and lets
me run other things without thinking too hard about it.
Now, without a keyboard, there's not much you can do with Emacs, but the controller settings in the Steam Deck actually give you a lot of levers to fix this. Any of the hardware buttons, touch pads, etc, can be set up to do things like show command menus, send keyboard and mouse events, change layers. Imagine QMK but you don't have to write any fucking code to do it.
I made some absolutely cursed shit. Naturally, I can't figure out how to share the layout directly.
Two layers: one for navigating to the SRS mode, one for navigating
and rating the cards. Pressing UP
on the
D-PAD changes between the two. (i know these don't rotate, i'm sorry,
it's not
my fault.)
With that, I can hit "R4" to open my SRS dashboard, then "A" to start
reviewing, then UP
to enter review mode. I
can mash B
to send C-g
.
In review mode, right bumper will flip the card for rating, left
bumper will quit. A
is "good" rating,
B
is "hard" rating, X
is "easy", A
is
"again, please".
This is a hilariously stupid system and I'm not sure I'll keep using it. It will, however, work, and allow me to study my flashcards in bed without getting distracted by my laptop and whatnot. It will not keep me from getting distracted by art of rally though.