{"id":26845608,"url":"https://github.com/rasendubi/dotfiles","last_synced_at":"2025-04-06T20:08:13.241Z","repository":{"id":10988955,"uuid":"13309248","full_name":"rasendubi/dotfiles","owner":"rasendubi","description":"My dotfiles","archived":false,"fork":false,"pushed_at":"2025-02-17T02:07:54.000Z","size":1644,"stargazers_count":227,"open_issues_count":0,"forks_count":83,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-03-30T19:38:13.862Z","etag":null,"topics":["dotfiles","emacs-configuration","nix-dotfiles","nixos","org-mode-configuration"],"latest_commit_sha":null,"homepage":"","language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rasendubi.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-10-03T20:38:16.000Z","updated_at":"2025-03-28T10:09:31.000Z","dependencies_parsed_at":"2024-12-09T01:22:32.916Z","dependency_job_id":"f97aac41-d352-400f-92e7-b037e6c35def","html_url":"https://github.com/rasendubi/dotfiles","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rasendubi%2Fdotfiles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rasendubi%2Fdotfiles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rasendubi%2Fdotfiles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rasendubi%2Fdotfiles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rasendubi","download_url":"https://codeload.github.com/rasendubi/dotfiles/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543588,"owners_count":20955865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dotfiles","emacs-configuration","nix-dotfiles","nixos","org-mode-configuration"],"created_at":"2025-03-30T19:37:33.603Z","updated_at":"2025-04-06T20:08:13.214Z","avatar_url":"https://github.com/rasendubi.png","language":"Emacs Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+PROPERTY: header-args :tangle yes :noweb yes :results silent\n#+STARTUP: overview\n\n* Introduction\nHi there! That's my dotfiles.\n\nThis repository contains configuration for three hosts:\n- omicron — my main laptop which runs NixOS\n- pie — my home server RPi running NixOS (see [[./pie.org][pie.org]])\n\nMost of config files are generated by [[http://orgmode.org/worg/org-contrib/babel/][org-babel]] from org files in this repository (yes, including this very same ~README.org~). That's [[https://en.wikipedia.org/wiki/Literate_programming][literate programming]] applied to dotfiles.\n\nThis file contains Emacs configuration, NixOS and home-manager configuration for omicron.\n\n[[./pie.org][pie.org]] is a separate NixOS config for pie host.\n\nTo generate actual nix files, you can open this file in Emacs, and execute =M-x org-babel-tangle=. Or from command line with the following command.\n\n#+begin_src sh :tangle no\nemacs README.org --batch -f org-babel-tangle\n#+end_src\n\n#+RESULTS:\n\nNote that you need to patch org-babel to correctly generate configs (\u003c\u003cpatch-ob-tangle\u003e\u003e)\n\nI keep generated files in sync with org files (so this repo is a valid Nix Flake), but they are not worth looking at—you'll have much better time reading this doc instead.\n\nPieces not (yet) covered in org files are:\n- scripts at =bin/=\n\n* Table of Contents :TOC_3:\n- [[#introduction][Introduction]]\n- [[#top-level][Top-level]]\n  - [[#flake][Flake]]\n  - [[#stable-packages][Stable packages]]\n  - [[#nixos][NixOS]]\n  - [[#home-manager][Home manager]]\n  - [[#packages][Packages]]\n  - [[#overlays][Overlays]]\n- [[#nixos-1][NixOS]]\n  - [[#general][General]]\n  - [[#re-expose-nixpkgs][Re-expose nixpkgs]]\n  - [[#sandbox][Sandbox]]\n  - [[#users][Users]]\n  - [[#machines][Machines]]\n    - [[#omicron][omicron]]\n- [[#emacs][Emacs]]\n  - [[#install-emacs][Install Emacs]]\n  - [[#bootstrap-emacs-config][Bootstrap Emacs config]]\n  - [[#patch-ob-tangle][Patch ob-tangle]]\n  - [[#gc-hacks][GC hacks]]\n  - [[#use-package][use-package]]\n  - [[#package][package]]\n  - [[#general-package][General (package)]]\n  - [[#dont-clutter-system][Don't clutter system]]\n  - [[#helpers][Helpers]]\n  - [[#ivy][ivy]]\n  - [[#smex][smex]]\n  - [[#counsel][counsel]]\n  - [[#avy][avy]]\n  - [[#imenu--imenu-list][imenu / imenu-list]]\n  - [[#wgrep][wgrep]]\n  - [[#whitespace][whitespace]]\n  - [[#whitespace-cleanup][whitespace-cleanup]]\n  - [[#which-key][which-key]]\n  - [[#google-translate][Google translate]]\n  - [[#tab-bar-mode][tab-bar-mode]]\n  - [[#highlight-current-line][Highlight current line]]\n  - [[#scrolling][Scrolling]]\n  - [[#visual-fill-column][visual-fill-column]]\n  - [[#misc][Misc]]\n- [[#environment][Environment]]\n  - [[#exwm][EXWM]]\n  - [[#window-management][Window management]]\n  - [[#window-layout][Window layout]]\n  - [[#screen-locking][Screen locking]]\n    - [[#slock][Slock]]\n    - [[#xss-lock][xss-lock]]\n    - [[#exwm-integration][EXWM integration]]\n  - [[#system-tray][System tray]]\n  - [[#screenshots][Screenshots]]\n  - [[#misc-1][Misc]]\n- [[#input][Input]]\n  - [[#keyboard][Keyboard]]\n    - [[#workman][Workman]]\n    - [[#keyboard-layout][Keyboard layout]]\n    - [[#xkeymap][Xkeymap]]\n    - [[#compose-keys][Compose keys]]\n    - [[#xcape][xcape]]\n    - [[#emacs-quail][Emacs quail]]\n  - [[#mouse][Mouse]]\n    - [[#python-listener][Python listener]]\n    - [[#emacs-handler][Emacs handler]]\n- [[#network][Network]]\n  - [[#networkmanager][NetworkManager]]\n  - [[#ssh][SSH]]\n    - [[#mosh][Mosh]]\n  - [[#dnsmasq][dnsmasq]]\n  - [[#firewall][Firewall]]\n- [[#services][Services]]\n  - [[#locate][Locate]]\n  - [[#gitolite][Gitolite]]\n  - [[#syncthing][Syncthing]]\n  - [[#docker][Docker]]\n  - [[#backup][Backup]]\n  - [[#direnv][direnv]]\n    - [[#flake-1][flake]]\n    - [[#direnv--lorri][direnv + lorri]]\n  - [[#virtualbox][VirtualBox]]\n- [[#hardware][Hardware]]\n  - [[#do-not-suspend-on-ac][Do not suspend on AC]]\n  - [[#autorandr][Autorandr]]\n  - [[#screen-brightness][Screen brightness]]\n  - [[#redshift][Redshift]]\n  - [[#pipewire][PipeWire]]\n  - [[#bluetooth][Bluetooth]]\n  - [[#adb][ADB]]\n  - [[#fwupd][fwupd]]\n- [[#browsers][Browsers]]\n  - [[#tridactyl][Tridactyl]]\n  - [[#edit-text-in-browser][Edit text in browser]]\n- [[#evil-mode][Evil-mode]]\n  - [[#general-1][General]]\n  - [[#swap-k-and-j][Swap k and j]]\n  - [[#evil-numbers][evil-numbers]]\n  - [[#evil-collection][evil-collection]]\n  - [[#evil-surrond][evil-surrond]]\n  - [[#calc][calc]]\n  - [[#evilify-compile-mode][Evilify compile mode]]\n  - [[#evilify-minibuffer][Evilify minibuffer]]\n  - [[#evilify-shell-mode][Evilify shell mode]]\n  - [[#lispyville][lispyville]]\n- [[#org-mode][Org-mode]]\n  - [[#general-2][General]]\n  - [[#todo][Todo]]\n  - [[#highlight-projects][Highlight projects]]\n  - [[#clocking][Clocking]]\n  - [[#capture][Capture]]\n    - [[#org-capture-keybindings][org-capture keybindings]]\n  - [[#capturing-images][Capturing images]]\n  - [[#datetree][datetree]]\n  - [[#cliplink][cliplink]]\n  - [[#refile][Refile]]\n    - [[#refiling-with-hydras][Refiling with hydras]]\n    - [[#refile-last-but-before-archive][Refile last but *before* archive]]\n  - [[#archive][Archive]]\n  - [[#agenda][Agenda]]\n    - [[#allow-next-projects-to-stuck][Allow NEXT projects to stuck]]\n  - [[#babel][Babel]]\n  - [[#latex-preview][Latex preview]]\n  - [[#image-preview][Image preview]]\n  - [[#export][Export]]\n  - [[#crypt][Crypt]]\n  - [[#org-list][org-list]]\n  - [[#org-checklist][org-checklist]]\n  - [[#habits][Habits]]\n  - [[#adaptive-wrap][adaptive-wrap]]\n  - [[#org-id][org-id]]\n  - [[#org-roam][org-roam]]\n    - [[#org-roam-exclude-org-fc][org-roam-exclude-org-fc]]\n    - [[#org-roam-slip-boxes][org-roam-slip-boxes]]\n    - [[#org-roam-node-display][org-roam-node-display]]\n    - [[#org-roam-buffer][org-roam-buffer]]\n    - [[#org-roam-dailies][org-roam-dailies]]\n    - [[#org-roam-protocol][org-roam-protocol]]\n    - [[#org-roam-graph][org-roam-graph]]\n    - [[#org-roam-kebab-slugs][org-roam-kebab-slugs]]\n    - [[#org-roam-update-ids][org-roam-update-ids]]\n    - [[#org-roam-new-node][org-roam-new-node]]\n  - [[#org-roam-ui][org-roam-ui]]\n  - [[#org-ref][org-ref]]\n  - [[#org-roam-bibtex][org-roam-bibtex]]\n  - [[#toc-org][toc-org]]\n  - [[#org-fc][org-fc]]\n  - [[#org-fc-review-todos][org-fc review todos]]\n  - [[#toggle-markupview][toggle markup/view]]\n  - [[#evilify-org-mode][Evilify org-mode]]\n  - [[#timestamps][Timestamps]]\n  - [[#fix-focus-steal-when-inserting--in-date-field][Fix focus steal when inserting “.” in date field]]\n- [[#mail-setup][Mail setup]]\n  - [[#applications][Applications]]\n  - [[#older-mbsync][Older mbsync]]\n  - [[#interface][Interface]]\n  - [[#emacs-1][Emacs]]\n- [[#applications-1][Applications]]\n  - [[#gpg][GPG]]\n    - [[#fix-epg--filter-revoked-keys-wrongly-filtering-out-my-keys][Fix epg--filter-revoked-keys wrongly filtering out my keys]]\n  - [[#yubikey][Yubikey]]\n  - [[#password-store][password-store]]\n  - [[#kde-apps][KDE apps]]\n  - [[#zathura][Zathura]]\n  - [[#user-applications][User applications]]\n- [[#development][Development]]\n  - [[#vim][Vim]]\n  - [[#terminal--shell][Terminal / shell]]\n    - [[#rxvt-unicode][rxvt-unicode]]\n    - [[#vterm][vterm]]\n    - [[#fish][fish]]\n    - [[#eshell][eshell]]\n    - [[#tmux][tmux]]\n    - [[#other-terminal-goodies][Other terminal goodies]]\n  - [[#git][git]]\n    - [[#git-config][git config]]\n    - [[#magit][magit]]\n    - [[#git-commit][git-commit]]\n    - [[#diff-hl][diff-hl]]\n  - [[#man-pages][Man pages]]\n  - [[#emacs-2][Emacs]]\n    - [[#use-spaces-for-indentation][Use spaces for indentation]]\n    - [[#make-underscore-part-of-words][Make underscore part of words]]\n    - [[#color-compilation-mode][Color compilation mode]]\n    - [[#projectile][projectile]]\n    - [[#company][company]]\n    - [[#hippie-expand][Hippie expand]]\n    - [[#flycheck][flycheck]]\n    - [[#flycheck-inline][flycheck-inline]]\n    - [[#electric-pair][electric-pair]]\n    - [[#color-identifiers][Color identifiers]]\n    - [[#dtrt-indent][dtrt-indent]]\n    - [[#paren-face][paren-face]]\n    - [[#lsp][LSP]]\n    - [[#commenting][Commenting]]\n    - [[#yasnippet][yasnippet]]\n  - [[#languages][Languages]]\n    - [[#emacs-lisp][Emacs lisp]]\n    - [[#nix][Nix]]\n    - [[#haskell][Haskell]]\n    - [[#rust][Rust]]\n    - [[#go][Go]]\n    - [[#cc][C/C++]]\n    - [[#cmake][CMake]]\n    - [[#python][Python]]\n    - [[#javascript][JavaScript]]\n    - [[#typescript][Typescript]]\n    - [[#vue][Vue]]\n    - [[#web-mode][Web-mode]]\n    - [[#clojurescript][Clojure(Script)]]\n    - [[#racket][Racket]]\n    - [[#groovy][Groovy]]\n    - [[#kotlin][Kotlin]]\n    - [[#forth][Forth]]\n    - [[#lua][Lua]]\n    - [[#ledger--hledger][Ledger / Hledger]]\n    - [[#markdown][Markdown]]\n    - [[#json][JSON]]\n    - [[#yaml][YAML]]\n    - [[#jinja2][Jinja2]]\n    - [[#docker-1][Docker]]\n    - [[#restclient][restclient]]\n    - [[#terraform][terraform]]\n    - [[#graphviz][graphviz]]\n    - [[#protobuf][protobuf]]\n    - [[#sql][SQL]]\n    - [[#plantuml][PlantUML]]\n    - [[#common-lisp][Common Lisp]]\n- [[#look-and-feel][Look and Feel]]\n  - [[#remove-the-clutter][Remove the clutter]]\n  - [[#beacon-mode][beacon-mode]]\n  - [[#fonts][Fonts]]\n  - [[#custom-input-font][Custom Input font]]\n  - [[#variable-pitch-fonts-in-org-mode][Variable-pitch fonts in org-mode]]\n    - [[#align-org-mode-tables-with-variable-pitch-fonts][Align org-mode tables with variable-pitch fonts]]\n  - [[#hi-dpi][Hi-DPI]]\n  - [[#color-theme][Color theme]]\n  - [[#emacs-modeline][Emacs modeline]]\n- [[#misc-2][Misc]]\n  - [[#quick-access][quick access]]\n  - [[#configure-path][Configure PATH]]\n\n* Top-level\n** Flake\nThis repository is nix flakes–compatible.\n\nThe following goes to ~flake.nix~ file.\n#+begin_src nix :tangle flake.nix :noweb no-export :padline no\n#\n# This file is auto-generated from \"README.org\"\n#\n{\n  description = \"rasendubi's packages and NixOS/home-manager configurations\";\n\n  inputs = {\n    nixpkgs = {\n      type = \"github\";\n      owner = \"NixOS\";\n      repo = \"nixpkgs\";\n      ref = \"nixos-24.11\";\n    };\n\n    \u003c\u003cflake-inputs\u003e\u003e\n  };\n\n  outputs = { self, ... }@inputs:\n    let\n      # Flakes are evaluated hermetically, thus are unable to access\n      # host environment (including looking up current system).\n      #\n      # That's why flakes must explicitly export sets for each system\n      # supported.\n      systems = [\"x86_64-linux\" \"aarch64-linux\" \"aarch64-darwin\"];\n\n      # genAttrs applies f to all elements of a list of strings, and\n      # returns an attrset { name -\u003e result }\n      #\n      # Useful for generating sets for all systems or hosts.\n      genAttrs = list: f: inputs.nixpkgs.lib.genAttrs list f;\n\n      # Generate pkgs set for each system. This takes into account my\n      # nixpkgs config (allowUnfree) and my overlays.\n      pkgsBySystem =\n        let mkPkgs = system: import inputs.nixpkgs {\n              inherit system;\n              overlays = self.overlays.${system};\n              config = {\n                allowUnfree = true;\n                input-fonts.acceptLicense = true;\n              };\n            };\n        in genAttrs systems mkPkgs;\n\n      # genHosts takes an attrset { name -\u003e options } and calls mkHost\n      # with options+name. The result is accumulated into an attrset\n      # { name -\u003e result }.\n      #\n      # Used in NixOS and Home Manager configurations.\n      genHosts = hosts: mkHost:\n        genAttrs (builtins.attrNames hosts) (name: mkHost ({ inherit name; } // hosts.${name}));\n\n      # merges a list of attrsets into a single attrset\n      mergeSections = inputs.nixpkgs.lib.foldr inputs.nixpkgs.lib.mergeAttrs {};\n\n    in mergeSections [\n      \u003c\u003cflake-outputs-nixos\u003e\u003e\n      \u003c\u003cflake-outputs-home-manager\u003e\u003e\n      \u003c\u003cflake-outputs-packages\u003e\u003e\n      \u003c\u003cflake-outputs-overlays\u003e\u003e\n      \u003c\u003cflake-outputs-nix-darwin\u003e\u003e\n    ];\n}\n#+end_src\n\nNix flakes are still an experimental feature, so you need the following in NixOS configuration to enable it.\n#+name: nixos-section\n#+begin_src nix\n{\n  nix = {\n    extraOptions = ''\n      experimental-features = nix-command flakes\n    '';\n  };\n}\n#+end_src\n\nFor nix-darwin systems:\n#+name: darwin-section\n#+begin_src nix\n{\n  nix = {\n    extraOptions = ''\n      experimental-features = nix-command flakes\n    '';\n  };\n}\n#+end_src\n\nFor non-NixOS system, put the following into =~/.config/nix/nix.conf=.\n#+begin_src conf :tangle no\nexperimental-features = nix-command flakes\n#+end_src\n\n** Unstable packages\nFor packages that are not available in latest stable, expose the unstable channel as ~pkgs.unstable~.\n\nAdd input:\n#+name: flake-inputs\n#+begin_src nix\nnixpkgs-unstable = {\n  type = \"github\";\n  owner = \"NixOS\";\n  repo = \"nixpkgs\";\n  ref = \"nixpkgs-unstable\";\n};\n#+end_src\n\nAdd overlay:\n#+name: flake-overlays\n#+begin_src nix\n(final: prev: {\n  unstable = import inputs.nixpkgs-unstable {\n    inherit system;\n    overlays = self.overlays.${system};\n    config = { allowUnfree = true; };\n  };\n})\n#+end_src\n** NixOS\nExpose NixOS configurations.\n#+name: flake-outputs-nixos\n#+begin_src nix\n(let\n  nixosHosts = {\n    omicron = { system = \"x86_64-linux\";  config = ./nixos-config.nix; };\n\n    # pie uses a separate config as it is very different\n    # from other hosts.\n    pie =     { system = \"aarch64-linux\"; config = ./pie.nix; };\n  };\n\n  mkNixosConfiguration = { name, system, config }:\n    let pkgs = pkgsBySystem.${system};\n    in inputs.nixpkgs.lib.nixosSystem {\n      inherit system;\n      modules = [\n        { nixpkgs = { inherit pkgs; }; }\n        (import config)\n      ];\n      specialArgs = { inherit name inputs; };\n    };\n\nin {\n  nixosConfigurations = genHosts nixosHosts mkNixosConfiguration;\n})\n#+end_src\n** Home manager\nAdd home-manager to flake inputs.\n#+name: flake-inputs\n#+begin_src nix\nhome-manager = {\n  type = \"github\";\n  owner = \"rycee\";\n  repo = \"home-manager\";\n  ref = \"release-24.11\";\n  inputs.nixpkgs.follows = \"nixpkgs\";\n};\n#+end_src\n\nExpose home-manager configurations.\n#+name: flake-outputs-home-manager\n#+begin_src nix\n(let\n  homeManagerHosts = {\n  };\n\n  mkHomeManagerConfiguration = { system, name, config, username, homeDirectory }:\n    let pkgs = pkgsBySystem.${system};\n    in inputs.home-manager.lib.homeManagerConfiguration {\n      inherit system pkgs username homeDirectory;\n      configuration = { lib, ... }: {\n        nixpkgs.config.allowUnfree = true;\n        nixpkgs.config.firefox.enableTridactylNative = true;\n        nixpkgs.overlays = self.overlays.${system};\n        imports = [\n          self.lib.home-manager-common\n\n          (import config)\n        ];\n      };\n    };\n\nin {\n  # Re-export common home-manager configuration to be reused between\n  # NixOS module and standalone home-manager config.\n  lib.home-manager-common = { name, lib, pkgs, config, ... }: {\n    imports = [\n      \u003c\u003chome-manager-section\u003e\u003e\n    ];\n    home.stateVersion = \"21.05\";\n  };\n  homeManagerConfigurations = genHosts homeManagerHosts mkHomeManagerConfiguration;\n})\n#+end_src\n\n#+name: home-manager-section\n#+begin_src nix\n{\n  options.hostname = lib.mkOption {\n    default = null;\n    type = lib.types.nullOr lib.types.str;\n    description = \"hostname so that other home-manager options can depend on it.\";\n  };\n}\n#+end_src\n\nIntegrate home-manager module into NixOS.\n#+name: nixos-section\n#+begin_src nix\n{\n  imports = [inputs.home-manager.nixosModules.home-manager];\n  home-manager = {\n    useUserPackages = true;\n    useGlobalPkgs = true;\n    users.rasen = inputs.self.lib.home-manager-common;\n  };\n}\n#+end_src\n\nIntegrate home-manager into nix-darwin.\n#+name: darwin-section\n#+begin_src nix\n{\n  imports = [inputs.home-manager.darwinModules.home-manager];\n  home-manager = {\n    useUserPackages = false;\n    useGlobalPkgs = true;\n    users.rasen = { ... }: {\n      imports = [\n        inputs.self.lib.home-manager-common\n        { hostname = config.networking.hostName; }\n      ];\n    };\n    # users.rasen = inputs.self.lib.home-manager-common;\n  };\n}\n#+end_src\n\n** nix-darwin\nAdd nix-darwin to flake inputs.\n#+name: flake-inputs\n#+begin_src nix\ndarwin = {\n  url = \"github:lnl7/nix-darwin/nix-darwin-24.11\";\n  inputs.nixpkgs.follows = \"nixpkgs\";\n};\n#+end_src\n\nAdd helper function to differentiate linux-only config:\n#+name: flake-overlays\n#+begin_src nix\n(final: prev: {\n  lib = prev.lib // {\n    linux-only = prev.lib.mkIf final.stdenv.isLinux;\n  };\n})\n#+end_src\n\n#+name: flake-outputs-nix-darwin\n#+begin_src nix\n(let\n  darwinHosts = {\n    \u003c\u003cdarwin-host\u003e\u003e\n  };\n\n  mkDarwinConfiguration = { name, system, modules ? [] }:\n    inputs.darwin.lib.darwinSystem {\n      inherit system;\n      modules = modules ++ [\n        { networking.hostName = name; }\n        self.darwin-common\n      ];\n    };\n\nin {\n  darwin-common = { lib, pkgs, config, ... }: {\n    imports = [\n      \u003c\u003cdarwin-section\u003e\u003e\n    ];\n  };\n\n  darwinConfigurations = genHosts darwinHosts mkDarwinConfiguration;\n})\n#+end_src\n\n#+name: darwin-section\n#+begin_src nix\n{\n  nixpkgs.config = {\n    allowUnfree = true;\n  };\n  nixpkgs.overlays = self.overlays.aarch64-darwin;\n  services.nix-daemon.enable = true;\n\n  system.stateVersion = 4;\n}\n#+end_src\n\nAnd enable this in home-manager ([[https://github.com/nix-community/home-manager/blob/master/modules/targets/darwin/linkapps.nix][home-manager/linkapps.nix at master · nix-community/home-manager · GitHub]]):\n#+name: home-manager-section\n#+begin_src nix\n({ config, lib, pkgs, ... }:\n\n{\n  config = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin {\n    # Install MacOS applications to the user environment.\n    home.file.\"Applications/Home Manager Apps\".source = let\n      apps = pkgs.buildEnv {\n        name = \"home-manager-applications\";\n        paths = config.home.packages;\n        pathsToLink = \"/Applications\";\n      };\n    in \"${apps}/Applications\";\n  };\n})\n#+end_src\n** Packages\nGenerate packages set for each supported system.\n#+name: flake-outputs-packages\n#+begin_src nix\n(let\n  mkPackages = system:\n    let\n      pkgs = pkgsBySystem.${system};\n    in\n      mergeSections [\n        \u003c\u003cflake-packages\u003e\u003e\n      ];\n\nin {\n  packages = genAttrs systems mkPackages;\n})\n#+end_src\n** Overlays\nGenerate overlays for all supported systems.\n#+name: flake-outputs-overlays\n#+begin_src nix\n(let\n  mkOverlays = system: [\n    # mix-in all local packages, so they are available as pkgs.${packages-name}\n    (final: prev: self.packages.${system})\n\n    \u003c\u003cflake-overlays\u003e\u003e\n  ];\nin {\n  overlays = genAttrs systems mkOverlays;\n})\n#+end_src\n\n~\u003c\u003cflake-overlays\u003e\u003e~ are defined elsewhere.\n* NixOS\n** General\nI'm a [[http://nixos.org/][NixOS]] user. What's cool about it is that I can describe all my system configuration in one file (almost). I can execute a single command and have a system with the same software, system settings, etc.\n\nAn outline of configuration looks like this:\n\n#+begin_src nix :tangle nixos-config.nix :noweb no-export :padline no\n#\n# This file is auto-generated from \"README.org\"\n#\n{ name, config, pkgs, lib, inputs, ... }:\nlet\n  machine-config = lib.getAttr name {\n    omicron = [\n      \u003c\u003cmachine-omicron\u003e\u003e\n    ];\n  };\n\nin\n{\n  imports = [\n    {\n      nixpkgs.config.allowUnfree = true;\n\n      # The NixOS release to be compatible with for stateful data such as databases.\n      system.stateVersion = \"21.05\";\n    }\n\n    \u003c\u003cnixos-section\u003e\u003e\n  ] ++ machine-config;\n}\n#+end_src\n\nThis =\u003c\u003cnixos-section\u003e\u003e= is replaced by other parts of this doc.\n\n** Re-expose nixpkgs\n#+name: nixos-section\n#+begin_src nix\n{\n  # for compatibility with nix-shell, nix-build, etc.\n  environment.etc.nixpkgs.source = inputs.nixpkgs;\n  nix.nixPath = [\"nixpkgs=/etc/nixpkgs\"];\n\n  # register self and nixpkgs as flakes for quick access\n  nix.registry = {\n    self.flake = inputs.self;\n\n    nixpkgs.flake = inputs.nixpkgs;\n  };\n}\n#+end_src\n\nSame but for Home Manager–managed host.\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.file.\"nixpkgs\".source = inputs.nixpkgs;\n  systemd.user.sessionVariables.NIX_PATH = pkgs.lib.linux-only (lib.mkForce \"nixpkgs=$HOME/nixpkgs\\${NIX_PATH:+:}$NIX_PATH\");\n\n  xdg.configFile.\"nix/registry.json\".text = builtins.toJSON {\n    version = 2;\n    flakes = [\n      {\n        from = { id = \"self\"; type = \"indirect\"; };\n        to = ({\n          type = \"path\";\n          path = inputs.self.outPath;\n        } // lib.filterAttrs\n          (n: v: n == \"lastModified\" || n == \"rev\" || n == \"revCount\" || n == \"narHash\")\n          inputs.self);\n      }\n      {\n        from = { id = \"nixpkgs\"; type = \"indirect\"; };\n        to = ({\n          type = \"path\";\n          path = inputs.nixpkgs.outPath;\n        } // lib.filterAttrs\n          (n: v: n == \"lastModified\" || n == \"rev\" || n == \"revCount\" || n == \"narHash\")\n          inputs.nixpkgs);\n      }\n    ];\n  };\n}\n#+end_src\n** Sandbox\nBuild all packages in sandbox:\n#+name: nixos-section\n#+begin_src nix\n{\n  nix.settings.sandbox = true;\n}\n#+end_src\n** Users\nI'm the only user of the system:\n\n#+name: nixos-section\n#+begin_src nix\n{\n  users.extraUsers.rasen = {\n    isNormalUser = true;\n    uid = 1000;\n    extraGroups = [ \"users\" \"wheel\" ];\n    initialPassword = \"HelloWorld\";\n  };\n  nix.settings.trusted-users = [\"rasen\"];\n}\n#+end_src\n\n=initialPassword= is used only first time when user is created. It must be changed as soon as possible with =passwd=.\n\n** Machines\n* macOS\n** Users\n#+name: darwin-section\n#+begin_src nix\n{\n  users.users.rasen = {\n    description = \"Oleksii Shmalko\";\n    home = \"/Users/rasen/\";\n  };\n}\n#+end_src\n\n** xbar integration function\n#+begin_src emacs-lisp\n(defun rasen/xbar (base \u0026optional arg)\n  \"Return a string for xbar plugin integration or perform the action.\"\n  ;; (message \"(rasen/xbar %S %S)\" base arg)\n  (pcase arg\n    ('clock-out (org-clock-out))\n    ('clock-in-last (org-clock-in-last))\n\n    ('nil (if (org-clock-is-active)\n              (concat\n               (format \"[%s] %s | length=50\\n\" (org-duration-from-minutes (org-clock-get-clocked-time)) org-clock-heading)\n               \"---\\n\"\n               (format \"Clock out | shell=%s | param1=clock-out\\n\" base))\n\n            (concat\n             (format \"not clocking %s | color=gray | size=12\\n\"\n                     (cond\n                      (break--work-timer (format \"w:%s\" (break--timer-to-string break--work-timer)))\n                      (break--rest-timer (format \"r:%s\" (break--timer-to-string break--rest-timer)))\n                      (t \"\")))\n             \"---\\n\"\n             (format \"Clock in last | shell=%s | param1=clock-in-last\\n\" base))))))\n#+end_src\n\norg-clock.1s.sh\n#+begin_src sh\n#!/usr/bin/env bash\n~/.nix-profile/bin/emacs --batch --eval \"(progn (require 'server) (princ (server-eval-at \\\"server\\\" '(rasen/xbar \\\"$0\\\"${1:+\" '$1\"}))))\"\n#+end_src\n** Yabai integration\n#+name: darwin-section\n#+begin_src nix\n{\n  services.yabai = {\n    enable = true;\n    package = pkgs.yabai;\n    config = {\n      layout = \"bsp\";\n      window_shadow = \"float\";\n      window_gap = 10;\n      focus_follows_mouse = \"autoraise\";\n      mouse_follows_focus = \"on\";\n      mouse_modifier = \"fn\";\n      mouse_action1 = \"move\";\n      mouse_action2 = \"resize\";\n    };\n    extraConfig = ''\n      yabai -m rule --add app=Emacs manage=on\n    '';\n  };\n}\n#+end_src\n\nSkhd integration to move between windows.\n\n[https://gist.github.com/ethan-leba/760054f36a2f7c144c6b06ab6458fae6]\n#+name: darwin-section\n#+begin_src nix\n{\n  environment.systemPackages = [ pkgs.skhd ];\n  services.skhd = {\n    enable = true;\n    skhdConfig = ''\n      :: default : emacsclient -e '(message \"default mode\")'\n      :: escaping : emacsclient -e '(message \"escaping mode\")'\n      # :: e : emacsclient -e '(message \"escape mode\")'\n      # :: es ; e\n      # #default \u003c lcmd - 0x2a ; e\n      # e \u003c lcmd - 0x2a ; default\n\n      default \u003c lcmd - 0x2A ; escaping\n      escaping \u003c lcmd - 0x2A ; default\n\n      # escaping \u003c lcmd - e ~\n\n      default \u003c cmd + ctrl - r : yabai -m space --layout $(yabai -m query --spaces --space | jq -r 'if .type == \"bsp\" then \"float\" else \"bsp\" end')\n      # default \u003c lcmd + ctrl - n : yabai -m window --ratio rel:-0.05\n      # default \u003c lcmd + ctrl - o : yabai -m window --ratio rel:+0.05\n      default \u003c lcmd + shift - n : yabai -m window --swap west\n      default \u003c lcmd + shift - u : yabai -m window --swap north\n      default \u003c lcmd + shift - e : yabai -m window --swap south\n      default \u003c lcmd + shift - o : yabai -m window --swap east\n      default \u003c lcmd - n [\n        ,* : yabai -m window --focus west\n        \"Emacs\" ~\n      ]\n      default \u003c lcmd - u [\n        ,* : yabai -m window --focus north\n        \"Emacs\" ~\n      ]\n      default \u003c lcmd - e [\n        ,* : yabai -m window --focus south\n        \"Emacs\" ~\n      ]\n      default \u003c lcmd - o [\n        ,* : yabai -m window --focus east\n        \"Emacs\" ~\n      ]\n    '';\n  };\n}\n#+end_src\n\nEmacs integration for yabai:\n#+begin_src emacs-lisp\n(defun yabai-move-on-error (direction move-fn)\n  \"Execute `move-fn'. If that function errors, move yabai focus in the specified direction.\"\n  (interactive)\n  (condition-case nil\n      (funcall move-fn)\n    (user-error (rasen/yabai-move direction))))\n\n(defun rasen/yabai-move (direction)\n  \"Move yabai focus in the specified `direction'.\"\n  (start-process \"yabai\" \"*yabai*\" \"yabai\" \"-m\" \"window\" \"--focus\" direction))\n\n(defun yabai-windmove-left ()\n  (interactive)\n  (yabai-move-on-error \"west\" #'windmove-left))\n\n(defun yabai-windmove-right ()\n  (interactive)\n  (yabai-move-on-error \"east\" #'windmove-right))\n\n(defun yabai-windmove-up ()\n  (interactive)\n  (yabai-move-on-error \"north\" #'windmove-up))\n\n(defun yabai-windmove-down ()\n  (interactive)\n  (yabai-move-on-error \"south\" #'windmove-down))\n#+end_src\n* Machines\n** omicron\nThis is my small Dell XPS 13 running NixOS.\n#+name: machine-omicron\n#+begin_src nix\n{\n  imports = [\n    (import \"${inputs.nixos-hardware}/dell/xps/13-9360\")\n    inputs.nixpkgs.nixosModules.notDetected\n  ];\n\n  boot.initrd.availableKernelModules = [ \"xhci_pci\" \"nvme\" \"usb_storage\" \"sd_mod\" \"rtsx_pci_sdmmc\" ];\n  boot.kernelModules = [ \"kvm-intel\" \"wl\" ];\n  boot.extraModulePackages = [ config.boot.kernelPackages.rtl88x2bu config.boot.kernelPackages.broadcom_sta ];\n\n  hardware.opengl = {\n    enable = true;\n    extraPackages = [\n      pkgs.vaapiIntel\n      pkgs.vaapiVdpau\n      pkgs.libvdpau-va-gl\n    ];\n  };\n\n  nix.settings.max-jobs = lib.mkDefault 4;\n\n  # powerManagement.cpuFreqGovernor = \"powersave\";\n\n  boot.loader.systemd-boot.enable = true;\n  boot.loader.efi.canTouchEfiVariables = true;\n}\n#+end_src\n\n~inputs.nixos-hardware~ comes from the following flake input.\n#+name: flake-inputs\n#+begin_src nix\nnixos-hardware = {\n  type = \"github\";\n  owner = \"NixOS\";\n  repo = \"nixos-hardware\";\n  flake = false;\n};\n#+end_src\n\nLVM on LUKS setup for disk encryption.\n#+name: machine-omicron\n#+begin_src nix\n{\n  boot.initrd.luks.devices = {\n    root = {\n      device = \"/dev/disk/by-uuid/8b591c68-48cb-49f0-b4b5-2cdf14d583dc\";\n      preLVM = true;\n    };\n  };\n  fileSystems.\"/boot\" = {\n    device = \"/dev/disk/by-uuid/BA72-5382\";\n    fsType = \"vfat\";\n  };\n  fileSystems.\"/\" = {\n    device = \"/dev/disk/by-uuid/434a4977-ea2c-44c0-b363-e7cf6e947f00\";\n    fsType = \"ext4\";\n    options = [ \"noatime\" \"nodiratime\" \"discard\" ];\n  };\n  fileSystems.\"/home\" = {\n    device = \"/dev/disk/by-uuid/8bfa73e5-c2f1-424e-9f5c-efb97090caf9\";\n    fsType = \"ext4\";\n    options = [ \"noatime\" \"nodiratime\" \"discard\" ];\n  };\n  swapDevices = [\n    { device = \"/dev/disk/by-uuid/26a19f99-4f3a-4bd5-b2ed-359bed344b1e\"; }\n  ];\n}\n#+end_src\n\nClickpad:\n#+name: machine-omicron\n#+begin_src nix\n{\n  services.xserver.libinput = {\n    enable = true;\n    touchpad.accelSpeed = \"0.7\";\n  };\n}\n#+end_src\n\nFix screen tearing ([[https://wiki.archlinux.org/index.php/intel_graphics#Tearing][ArchWiki]]):\n#+name: machine-omicron\n#+begin_src nix\n{\n  services.xserver.config = ''\n    Section \"Device\"\n      Identifier \"Intel Graphics\"\n      Driver \"intel\"\n\n      Option \"TearFree\" \"true\"\n      Option \"TripleBuffer\" \"true\"\n    EndSection\n  '';\n}\n#+end_src\n** bayraktar\nbayraktar is macbook pro managed with nix-darwin.\n#+name: darwin-host\n#+begin_src nix\nbayraktar = {\n  system = \"aarch64-darwin\";\n};\n#+end_src\n\n*** Work email\n#+name: home-manager-section\n#+begin_src nix\n{\n  accounts.email.accounts = lib.mkIf (config.hostname == \"bayraktar\") (lib.mkForce {\n    fluxon = {\n      realName = \"Oleksii Shmalko\";\n      address = \"oleksii@fluxon.com\";\n      flavor = \"gmail.com\";\n      primary = true;\n\n      passwordCommand = \"pass fluxon/google.com/as@fluxon.com/email\";\n      maildir.path = \"fluxon\";\n\n      msmtp.enable = true;\n      notmuch.enable = true;\n      mbsync.enable = true;\n      mbsync.create = \"maildir\";\n    };\n  });\n  programs.mbsync.extraConfig = lib.mkForce \"\";\n  programs.notmuch.extraConfig = {\n    index.\"header.Sender\" = \"Sender\";\n  };\n}\n#+end_src\n\n* Emacs\n** Install Emacs\n#+name: install-emacs\n\nI use emacs from [[https://github.com/nix-community/emacs-overlay][emacs-overlay]].\n#+name: flake-inputs\n#+begin_src nix\nemacs-overlay = {\n  type = \"github\";\n  owner = \"nix-community\";\n  repo = \"emacs-overlay\";\n};\n#+end_src\n\nUse overlay (~\u003c\u003cflake-overlays\u003e\u003e~).\n#+name: flake-overlays\n#+begin_src nix\ninputs.emacs-overlay.overlay\n#+end_src\n\nExpose Emacs with my packages as a top-level package (~\u003c\u003cflake-packages\u003e\u003e~).\n#+name: flake-packages\n#+begin_src nix\n(let\n  emacs-base =\n    if pkgs.stdenv.isDarwin\n    then pkgs.emacs.overrideAttrs (old: {\n      patches =\n        (old.patches or [])\n        ++ [\n          # Fix OS window role so that yabai can pick up emacs\n          (pkgs.fetchpatch {\n            url = \"https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-28/fix-window-role.patch\";\n            sha256 = \"sha256-+z/KfsBm1lvZTZNiMbxzXQGRTjkCFO4QPlEK35upjsE=\";\n          })\n          # Use poll instead of select to get file descriptors\n          # (pkgs.fetchpatch {\n          #   url = \"https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-29/poll.patch\";\n          #   sha256 = \"sha256-jN9MlD8/ZrnLuP2/HUXXEVVd6A+aRZNYFdZF8ReJGfY=\";\n          # })\n          # Enable rounded window with no decoration\n          (pkgs.fetchpatch {\n            url = \"https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-29/round-undecorated-frame.patch\";\n            sha256 = \"sha256-uYIxNTyfbprx5mCqMNFVrBcLeo+8e21qmBE3lpcnd+4=\";\n          })\n          # Make emacs aware of OS-level light/dark mode\n          (pkgs.fetchpatch {\n            url = \"https://raw.githubusercontent.com/d12frosted/homebrew-emacs-plus/master/patches/emacs-28/system-appearance.patch\";\n            sha256 = \"sha256-oM6fXdXCWVcBnNrzXmF0ZMdp8j0pzkLE66WteeCutv8=\";\n          })\n        ];\n      configureFlags =\n        (old.configureFlags or [])\n        ++ [\n          \"LDFLAGS=-headerpad_max_install_names\"\n        ];\n    })\n    else pkgs.emacs.override {\n      withX = true;\n      # select lucid toolkit\n      toolkit = \"lucid\";\n      withGTK2 = false; withGTK3 = false;\n    };\n  emacs-packages = (epkgs:\n    (with epkgs.melpaPackages; [\n\n      activity-watch-mode\n      aggressive-indent\n      atomic-chrome\n      avy\n      bash-completion\n      beacon\n      blacken\n      cider\n      clojure-mode\n      cmake-mode\n      color-identifiers-mode\n      company\n      company-box\n      counsel\n      counsel-projectile\n      dart-mode\n      diff-hl\n      diminish\n      direnv\n      dockerfile-mode\n      doom-modeline\n      dtrt-indent\n      edit-indirect\n      el-patch\n      elpy\n      emojify\n      envrc\n      epresent\n      evil\n      evil-collection\n      evil-numbers\n      evil-org\n      evil-surround\n      evil-swap-keys\n      exec-path-from-shell\n      expand-region\n      fish-completion\n      fish-mode\n      flycheck\n      flycheck-inline\n      flycheck-jest\n      flycheck-rust\n      forge\n      forth-mode\n      just-mode\n      justl\n      general\n      go-mode\n      google-translate\n      gptel\n      graphviz-dot-mode\n      groovy-mode\n      haskell-mode\n      imenu-list\n      ivy\n      ivy-bibtex\n      ivy-pass\n      jinja2-mode\n      js2-mode\n      json-mode\n      ledger-mode\n      lispyville\n      lsp-haskell\n      lsp-mode\n      lsp-ui\n      lua-mode\n      magit\n      markdown-mode\n      modus-themes\n      nix-mode\n      nix-sandbox\n      notmuch\n      ol-notmuch\n      org-cliplink\n      org-download\n      org-drill\n      org-ref\n      org-roam\n      org-roam-bibtex\n      org-super-agenda\n      paren-face\n      pass\n      php-mode\n      pip-requirements\n      plantuml-mode\n      prettier-js\n      projectile\n      protobuf-mode\n      psc-ide\n      purescript-mode\n      py-autopep8\n      racer\n      racket-mode\n      restclient\n      rjsx-mode\n      ryo-modal\n      god-mode\n      multiple-cursors\n      rust-mode\n      slime\n      smex\n      spaceline\n      svelte-mode\n      swift-mode\n      terraform-mode\n      tide\n      toc-org\n      typescript-mode\n      visual-fill-column\n      vterm\n      vue-mode\n      w3m\n      web-mode\n      wgrep\n      which-key\n      whitespace-cleanup-mode\n      writegood-mode\n      yaml-mode\n      yasnippet\n      zig-mode\n\n      corfu\n      cape\n      vertico\n      orderless\n      consult\n      embark\n      marginalia\n      smartparens\n      git-link\n\n    ]) ++\n    [\n      epkgs.elpaPackages.org\n      epkgs.nongnuPackages.org-contrib\n      epkgs.elpaPackages.adaptive-wrap\n      epkgs.exwm\n      # not available in melpa\n      epkgs.elpaPackages.valign\n\n      epkgs.elpaPackages.eglot\n\n      (epkgs.trivialBuild rec {\n        pname = \"org-roam-ui\";\n        version = \"20210830\";\n        src = pkgs.fetchFromGitHub {\n          owner = \"org-roam\";\n          repo = \"org-roam-ui\";\n          rev = \"9ad111d2102c24593f6ac012206bb4b2c9c6c4e1\";\n          sha256 = \"sha256-x6notv/U+y9Es8m58R/Qh7GEAtRqXqXvr7gy5OiDDUM=\";\n        };\n        packageRequires = [\n          epkgs.melpaPackages.f\n          epkgs.melpaPackages.org-roam\n          epkgs.melpaPackages.websocket\n          epkgs.melpaPackages.simple-httpd\n        ];\n\n        postInstall = ''\n          cp -r ./out/ $LISPDIR/\n        '';\n\n        meta = {\n          description = \"A graphical frontend for exploring your org-roam Zettelkasten\";\n          license = pkgs.lib.licenses.gpl3;\n        };\n      })\n\n      (epkgs.trivialBuild rec {\n        pname = \"org-fc\";\n        version = \"20201121\";\n        src = pkgs.fetchFromGitHub {\n          # owner = \"rasendubi\";\n          # repo = \"org-fc\";\n          # rev = \"35ec13fd0412cd17cbf0adba7533ddf0998d1a90\";\n          # sha256 = \"sha256-2h1dIR7WHYFsLZ/0D4HgkoNDxKQy+v3OaiiCwToynvU=\";\n          owner = \"l3kn\";\n          repo = \"org-fc\";\n          rev = \"cc191458a991138bdba53328690a569b8b563502\";\n          sha256 = \"sha256-wzMSgS4iZfpKOICqQQuQYNPb2h7i4tTWsMs7mVmgBt8=\";\n        };\n        packageRequires = [\n          epkgs.elpaPackages.org\n          epkgs.melpaPackages.hydra\n        ];\n        propagatedUserEnvPkgs = [ pkgs.findutils pkgs.gawk ];\n\n        postInstall = ''\n          cp -r ./awk/ $LISPDIR/\n        '';\n\n        meta = {\n          description = \"Spaced Repetition System for Emacs org-mode\";\n          license = pkgs.lib.licenses.gpl3;\n        };\n      })\n\n      # required for org-roam/emacsql-sqlite3\n      pkgs.sqlite\n\n      pkgs.notmuch\n      pkgs.w3m\n      pkgs.imagemagick\n      pkgs.shellcheck\n\n      (pkgs.python3.withPackages (pypkgs: [\n        pypkgs.autopep8\n        pypkgs.black\n        pypkgs.flake8\n        pypkgs.mypy\n        pypkgs.pylint\n        pypkgs.virtualenv\n      ]))\n\n      (pkgs.aspellWithDicts (dicts: with dicts; [en en-computers en-science uk]))\n\n      # latex for displaying fragments in org-mode\n      (pkgs.texlive.combine {\n        inherit (pkgs.texlive)\n          scheme-small\n          dvipng\n          dvisvgm\n          mhchem # chemistry\n          tikz-cd # category theory diagrams\n          # required for org export\n          wrapfig\n          capt-of\n        ;\n      })\n      pkgs.ghostscript\n    ]\n  );\n\n  overrides = self: super: {\n    # select org from elpa\n    org = super.elpaPackages.org;\n  };\n\n  emacs-final = ((pkgs.emacsPackagesFor emacs-base).overrideScope overrides).emacsWithPackages emacs-packages;\n\nin {\n  my-emacs = emacs-final // {\n    base = emacs-base;\n    overrides = overrides;\n    packages = emacs-packages;\n  };\n})\n#+end_src\n\nInstall Emacs with Home manager (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  programs.emacs = {\n    enable = true;\n    package = pkgs.my-emacs.base;\n    extraPackages = pkgs.my-emacs.packages;\n    overrides = pkgs.my-emacs.overrides;\n  };\n  # services.emacs.enable = true;\n\n  # fonts used by emacs\n  home.packages = [\n    pkgs.input-mono\n    pkgs.libertine\n  ];\n}\n#+end_src\n\n** Bootstrap Emacs config\nBesides tangling into Flake/NixOS configuration files, this file /is/ Emacs configuration.\n\nEmacs does not source this file automatically, so I need to instruct it to do so.\nCheck [[https://orgmode.org/worg/org-contrib/babel/intro.html#emacs-initialization][org-babel documentation]] for more info.\nThe following snippet is an adaptation of that idea and goes to my ~.emacs.d/init.el~.\n#+begin_src emacs-lisp :tangle .emacs.d/init.el\n;;\n;; This file is auto-generated from \"README.org\"\n;;\n\n(defvar rasen/dotfiles-directory\n  (file-name-as-directory\n   (expand-file-name \"..\" (file-name-directory (file-truename user-init-file))))\n  \"The path to the dotfiles directory.\")\n\n(require 'org)\n(require 'ob-tangle)\n\n\u003c\u003cob-tangle-patch\u003e\u003e\n\n(org-babel-load-file (expand-file-name \"README.org\" rasen/dotfiles-directory))\n#+end_src\n\nAnother important file that needs to be tangled is [[./.emacs.d/early-init.el]]. For now, just add a header to it.\n#+begin_src emacs-lisp :tangle .emacs.d/early-init.el\n;;;\n;;; This file is auto-generated from \"README.org\"\n;;;\n#+end_src\n** Patch ob-tangle\n#+name: patch-ob-tangle\n*This patch is critical to getting this config working. Without it, org-babel will tangle this file incorrectly*\n\nThis patches ob-tangle to allow defining sections with the same name multiple times. All sections with the same name are concatenated. (This was the default behavior some time ago, so this restores it.)\n(~\u003c\u003cob-tangle-patch\u003e\u003e~)\n#+name: ob-tangle-patch\n#+begin_src emacs-lisp :noweb no\n(require 'el-patch)\n;; org-babel fixes to tangle ALL matching sections\n(defun rasen/map-regex (regex fn)\n  \"Map the REGEX over the BUFFER executing FN.\n\nFN is called with the match-data of the regex.\n\nReturns the results of the FN as a list.\"\n  (save-excursion\n    (goto-char (point-min))\n    (let (res)\n      (save-match-data\n        (while (re-search-forward regex nil t)\n          (let ((f (match-data)))\n            (setq res\n                  (append res\n                          (list\n                           (save-match-data\n                             (funcall fn f))))))))\n      res)))\n\n(el-patch-feature ob-core)\n(el-patch-defun org-babel-expand-noweb-references (\u0026optional info parent-buffer)\n  \"Expand Noweb references in the body of the current source code block.\n\nFor example the following reference would be replaced with the\nbody of the source-code block named `example-block'.\n\n\u003c\u003cexample-block\u003e\u003e\n\nNote that any text preceding the \u003c\u003cfoo\u003e\u003e construct on a line will\nbe interposed between the lines of the replacement text.  So for\nexample if \u003c\u003cfoo\u003e\u003e is placed behind a comment, then the entire\nreplacement text will also be commented.\n\nThis function must be called from inside of the buffer containing\nthe source-code block which holds BODY.\n\nIn addition the following syntax can be used to insert the\nresults of evaluating the source-code block named `example-block'.\n\n\u003c\u003cexample-block()\u003e\u003e\n\nAny optional arguments can be passed to example-block by placing\nthe arguments inside the parenthesis following the convention\ndefined by `org-babel-lob'.  For example\n\n\u003c\u003cexample-block(a=9)\u003e\u003e\n\nwould set the value of argument \\\"a\\\" equal to \\\"9\\\".  Note that\nthese arguments are not evaluated in the current source-code\nblock but are passed literally to the \\\"example-block\\\".\"\n  (let* ((parent-buffer (or parent-buffer (current-buffer)))\n         (info (or info (org-babel-get-src-block-info 'light)))\n         (lang (nth 0 info))\n         (body (nth 1 info))\n         (comment (string= \"noweb\" (cdr (assq :comments (nth 2 info)))))\n         (noweb-re (format \"\\\\(.*?\\\\)\\\\(%s\\\\)\"\n                           (with-current-buffer parent-buffer\n                             (org-babel-noweb-wrap))))\n         (cache nil)\n         (c-wrap\n          (lambda (s)\n            ;; Comment string S, according to LANG mode.  Return new\n            ;; string.\n            (unless org-babel-tangle-uncomment-comments\n              (with-temp-buffer\n                (funcall (org-src-get-lang-mode lang))\n                (comment-region (point)\n                                (progn (insert s) (point)))\n                (org-trim (buffer-string))))))\n         (expand-body\n          (lambda (i)\n            ;; Expand body of code represented by block info I.\n            (let ((b (if (org-babel-noweb-p (nth 2 i) :eval)\n                         (org-babel-expand-noweb-references i)\n                       (nth 1 i))))\n              (if (not comment) b\n                (let ((cs (org-babel-tangle-comment-links i)))\n                  (concat (funcall c-wrap (car cs)) \"\\n\"\n                          b \"\\n\"\n                          (funcall c-wrap (cadr cs))))))))\n         (expand-references\n          (lambda (ref cache)\n            (pcase (gethash ref cache)\n              (`(,last . ,previous)\n               ;; Ignore separator for last block.\n               (let ((strings (list (funcall expand-body last))))\n                 (dolist (i previous)\n                   (let ((parameters (nth 2 i)))\n                     ;; Since we're operating in reverse order, first\n                     ;; push separator, then body.\n                     (push (or (cdr (assq :noweb-sep parameters)) \"\\n\")\n                           strings)\n                     (push (funcall expand-body i) strings)))\n                 (mapconcat #'identity strings \"\")))\n              ;; Raise an error about missing reference, or return the\n              ;; empty string.\n              ((guard (or org-babel-noweb-error-all-langs\n                          (member lang org-babel-noweb-error-langs)))\n               (error \"Cannot resolve %s (see `org-babel-noweb-error-langs')\"\n                      (org-babel-noweb-wrap ref)))\n              (_ \"\")))))\n    (replace-regexp-in-string\n     noweb-re\n     (lambda (m)\n       (with-current-buffer parent-buffer\n         (save-match-data\n           (let* ((prefix (match-string 1 m))\n                  (id (match-string 3 m))\n                  (evaluate (string-match-p \"(.*)\" id))\n                  (expansion\n                   (cond\n                    (evaluate\n                     ;; Evaluation can potentially modify the buffer\n                     ;; and invalidate the cache: reset it.\n                     (setq cache nil)\n                     (let ((raw (org-babel-ref-resolve id)))\n                       (if (stringp raw) raw (format \"%S\" raw))))\n                    ;; Retrieve from the Library of Babel.\n                    ((nth 2 (assoc-string id org-babel-library-of-babel)))\n                    ;; Return the contents of headlines literally.\n                    ((org-babel-ref-goto-headline-id id)\n                     (org-babel-ref-headline-body))\n                    ;; Look for a source block named SOURCE-NAME.  If\n                    ;; found, assume it is unique; do not look after\n                    ;; `:noweb-ref' header argument.\n                    ((org-with-point-at 1\n                       (let ((r (org-babel-named-src-block-regexp-for-name id)))\n                         (and (re-search-forward r nil t)\n                              (not (org-in-commented-heading-p))\n                              (el-patch-swap\n                                (funcall expand-body\n                                         (org-babel-get-src-block-info t))\n                                (mapconcat\n                                 #'identity\n                                 (rasen/map-regex r\n                                                  (lambda (md)\n                                                    (funcall expand-body\n                                                             (org-babel-get-src-block-info t))))\n                                 \"\\n\"))))))\n                    ;; All Noweb references were cached in a previous\n                    ;; run.  Extract the information from the cache.\n                    ((hash-table-p cache)\n                     (funcall expand-references id cache))\n                    ;; Though luck.  We go into the long process of\n                    ;; checking each source block and expand those\n                    ;; with a matching Noweb reference.  Since we're\n                    ;; going to visit all source blocks in the\n                    ;; document, cache information about them as well.\n                    (t\n                     (setq cache (make-hash-table :test #'equal))\n                     (org-with-wide-buffer\n                      (org-babel-map-src-blocks nil\n                        (if (org-in-commented-heading-p)\n                            (org-forward-heading-same-level nil t)\n                          (let* ((info (org-babel-get-src-block-info t))\n                                 (ref (cdr (assq :noweb-ref (nth 2 info)))))\n                            (push info (gethash ref cache))))))\n                     (funcall expand-references id cache)))))\n             ;; Interpose PREFIX between every line.\n             (mapconcat #'identity\n                        (split-string expansion \"[\\n\\r]\")\n                        (concat \"\\n\" prefix))))))\n     body t t 2)))\n#+end_src\n** GC hacks\nSuppress GC in early init and restore it after init is complete. (~.emacs.d/early-init.el~)\n#+begin_src emacs-lisp :tangle .emacs.d/early-init.el\n(setq gc-cons-threshold most-positive-fixnum)\n(add-hook 'emacs-startup-hook (defun rasen/restore-gc-threshold ()\n                                (setq gc-cons-threshold 800000)))\n#+end_src\n** use-package\n[[https://github.com/jwiegley/use-package][use-package]] is a cool emacs library that helps managing emacs configuration making it simpler and more structured. (emacs-lisp)\n#+begin_src emacs-lisp\n;; Do not ensure packages---they are installed with Nix\n(setq use-package-always-ensure nil)\n;; (setq use-package-verbose t)\n(eval-when-compile\n  (require 'use-package))\n(require 'bind-key)\n(require 'diminish)\n#+end_src\n** package\nAll emacs packages are installed with Nix. (See \u003c\u003cinstall-emacs\u003e\u003e.)\nDisable usage of emacs internal archives. (~.emacs.d/early-init.el~)\n#+begin_src emacs-lisp :tangle .emacs.d/early-init.el\n(require 'package)\n(setq package-archives nil)\n(setq package-enable-at-startup nil)\n#+end_src\n\n** Hard way\nHard way: prohibit usage of keybindings I have more efficient bindings for.\n#+begin_src emacs-lisp\n(defmacro rasen/hard-way (key)\n  \"Prohibit usage of a keybinding and redirect to use `key' instead.\"\n  `(lambda () (interactive) (error \"Don't use this key! Use %s instead\" ,key)))\n#+end_src\n\n** General (package)\n\nI use [[https://github.com/noctuid/general.el][general]] to define my keybindings. (emacs-lisp)\n#+begin_src emacs-lisp\n\u003c\u003cbefore-general\u003e\u003e\n\n(use-package general)\n\n;; Definer for my leader\n(general-create-definer --leader-def   :prefix \"SPC\")\n(general-create-definer --s-leader-def :keymaps '(motion insert emacs) :prefix \"s-SPC\" :non-normal-prefix \"s-SPC\")\n\n;; Extra-hackery to define key with multiple prefixes\n(defmacro leader-def (\u0026rest args)\n  (declare (indent defun))\n  `(progn (--leader-def   ,@args)\n          (--s-leader-def ,@args)))\n\n;; Definer for my leader + applied globally across all windows.\n(general-create-definer s-leader-def\n  :keymaps '(motion emacs insert) :prefix \"SPC\"\n  :non-normal-prefix \"s-SPC\"\n  :global-prefix \"s-SPC\")\n\n(s-leader-def \"\" nil)\n\n(with-eval-after-load \"evil\"\n  ;; free-up prefix\n  (s-leader-def :keymaps '(motion normal visual) \"\" nil))\n#+end_src\n\n** General keybindings\n\nI use a custom modal mode.\n\n#+begin_src emacs-lisp\n(use-package ryo-modal\n  :disabled t\n  :config\n  (with-eval-after-load 'org\n    (setq org-support-shift-select 'always))\n\n  (defun rasen/shift-translate ()\n    \"Make the following commands behave as if shift-translated.\"\n    (interactive)\n    (setq this-command-keys-shift-translated t))\n\n  (defun rasen/dwim-beginning-of-line-paragraph ()\n    (interactive \"^\")\n    (let ((orig-point (point)))\n      (if (eq orig-point (line-beginning-position))\n          (backward-paragraph)\n        (back-to-indentation)\n        (when (= orig-point (point))\n          (move-beginning-of-line 1)))))\n\n  (defun rasen/extend-beginning-of-line-paragraph ()\n    (interactive)\n    (let ((this-command-keys-shift-translated t))\n      (call-interactively 'rasen/dwim-beginning-of-line-paragraph)))\n\n  (defun rasen/dwim-end-of-line-paragraph ()\n    (interactive \"^\")\n    (let ((orig-point (point)))\n      (if (eq orig-point (line-end-position))\n          (forward-paragraph)\n        (end-of-line))))\n  (defun rasen/extend-end-of-line-paragraph ()\n    (interactive)\n    (let ((this-command-keys-shift-translated t))\n      (call-interactively 'rasen/dwim-end-of-line-paragraph)))\n\n  (let ((keys\n         '((\"n\" \"\u003cleft\u003e\")\n           (\"N\" \"S-\u003cleft\u003e\")\n           (\"o\" \"\u003cright\u003e\")\n           (\"O\" \"S-\u003cright\u003e\")\n           (\"u\" \"\u003cup\u003e\")\n           (\"U\" \"S-\u003cup\u003e\")\n           (\"e\" \"\u003cdown\u003e\")\n           (\"E\" \"S-\u003cdown\u003e\")\n           (\"f\" \"M-b\")\n           (\"F\" \"M-S-b\")\n           (\"p\" \"M-f\")\n           (\"P\" \"M-S-f\"))))\n    (mapc (lambda (it)\n            (define-key ryo-modal-mode-map (kbd (car it)) (kbd (cadr it))))\n          keys))\n  (global-set-key (kbd \"\u003cescape\u003e\") (defun rasen/enable-ryo ()\n                                     (interactive)\n                                     (ryo-modal-mode 1)))\n\n  (define-key ryo-modal-mode-map (kbd \"Y\") nil)\n\n  (ryo-modal-keys\n   (\"t\" ryo-modal-mode)\n   (\"y\" rasen/dwim-beginning-of-line-paragraph)\n   (\"Y\" rasen/dwim-beginning-of-line-paragraph :first '(rasen/shift-translate))\n   (\"i\" rasen/dwim-end-of-line-paragraph)\n   (\"I\" rasen/dwim-end-of-line-paragraph :first '(rasen/shift-translate))))\n#+end_src\n** Don't clutter system\nSave custom configuration in the =~/.emacs.d/custom.el= file so emacs does not clutter =init.el=.\n#+begin_src emacs-lisp\n(setq custom-file (expand-file-name \"custom.el\" user-emacs-directory))\n(load custom-file t)\n#+end_src\n\nDon't clutter the current directory with backups. Save them in a separate directory.\n#+begin_src emacs-lisp\n(setq backup-directory-alist '((\".\" . \"~/.emacs.d/backups\")))\n#+end_src\n\nDon't clutter the current directory with auto-save files.\n#+begin_src emacs-lisp\n(setq auto-save-file-name-transforms '((\".*\" \"~/.emacs.d/backups/\" t)))\n#+end_src\n\nDo not create lockfiles either. (I am the only user in the system and only use emacs through daemon, so that should be ok.)\n#+begin_src emacs-lisp\n(setq create-lockfiles nil)\n#+end_src\n** Helpers\nEmacs lisp helper functions.\n\nTimestamp-ids are used to uniquely identify things.\n#+begin_src emacs-lisp\n(defun rasen/tsid (\u0026optional time)\n  \"Return timestamp-id.\"\n  (format-time-string \"%Y%m%d%H%M%S\" time \"UTC\"))\n\n(defun rasen/insert-tsid ()\n  \"Insert timestamp-id at point.\"\n  (interactive)\n  (insert (rasen/tsid)))\n#+end_src\n\nInsert current date in =yyyy-mm-dd= format. Useful when creating dated notes or dumb commits.\n#+begin_src emacs-lisp\n(defun rasen/insert-date (arg)\n  \"Insert current date. With prefix ARG, insert time in ISO 8601 format as well. With double-prefix, insert time in UTC timezone.\"\n  (interactive \"p\")\n  (insert (format-time-string\n           (if (\u003e arg 1)\n               \"%FT%T%z\"\n             \"%F\")\n           nil\n           (if (equal arg 16) t nil))))\n\n(general-def 'insert '(git-commit-mode-map ivy-minibuffer-map)\n  \"C-c .\" #'rasen/insert-date)\n(general-def 'ivy-minibuffer-map\n  \"C-c .\" #'rasen/insert-date)\n\n(general-def 'normal 'org-mode-map\n  \"RET .\" #'rasen/insert-date)\n#+end_src\n\n#+begin_src emacs-lisp\n(defun rasen/copy-file-path ()\n  \"Copy the current buffer's path to kill ring.\"\n  (interactive)\n  ;; TODO: optionally strip project path\n  (kill-new (buffer-file-name)))\n#+end_src\n\n#+begin_src emacs-lisp\n(defun rasen/org-copy-log-entry (arg)\n  \"Copy the current org entry as a log line with timestamp.\n\nThe transformation is as follows:\n\n,* I am entry\n:PROPERTIES:\nCREATED: [2020-06-01]\n:END:\n\nbecomes\n\n- [2020-06-01] I am entry\n\nIf ARG is provided, kill the entry.\"\n  (interactive \"P\")\n  (let* ((heading (org-get-heading))\n         (created (org-entry-get (point) \"CREATED\"))\n         (line (concat \"- \" created \" \" heading)))\n    (when arg\n      (org-cut-subtree)\n      (current-kill 1))\n    (kill-new (concat line \"\\n\"))\n    (message line)))\n#+end_src\n\nShamelessly stolen from https://github.com/purcell/emacs.d.\n#+begin_src emacs-lisp\n(defun rename-this-file-and-buffer (new-name)\n  \"Renames both current buffer and file it's visiting to NEW-NAME.\"\n  (interactive \"FNew name: \")\n  (let ((name (buffer-name))\n        (filename (buffer-file-name)))\n    (unless filename\n      (error \"Buffer '%s' is not visiting file!\" name))\n    (if (get-buffer new-name)\n        (message \"A buffer named '%s' already exists!\" new-name)\n      (progn\n        (make-directory (file-name-directory new-name) t)\n        (when (file-exists-p filename)\n          (rename-file filename new-name 1))\n        (rename-buffer new-name)\n        (set-visited-file-name new-name)))))\n\n(defun delete-this-file-and-buffer ()\n  \"Delete the current file, and kill the buffer.\"\n  (interactive)\n  (or (buffer-file-name) (error \"No file is currently being edited\"))\n  (when (yes-or-no-p (format \"Really delete '%s'?\"\n                             (file-name-nondirectory buffer-file-name)))\n    (delete-file (buffer-file-name))\n    (kill-buffer)))\n#+end_src\n\n#+begin_src emacs-lisp\n(defun add-to-path (str)\n  \"Add an STR to the PATH environment variable.\"\n  (setenv \"PATH\" (concat str \":\" (getenv \"PATH\"))))\n#+end_src\n** ivy\n#+begin_src emacs-lisp\n(use-package ivy\n  :disabled t\n  :demand\n  :general\n  (s-leader-def\n    \"b\"  #'ivy-switch-buffer)\n  :diminish ivy-mode\n  :config\n  \u003c\u003civy-config\u003e\u003e\n  (ivy-mode 1)\n  )\n#+end_src\n\nDo not start input with =^= and ignore the case.\n#+name: ivy-config\n#+begin_src emacs-lisp :tangle no\n(setq-default ivy-initial-inputs-alist nil)\n(setq-default ivy-re-builders-alist '((t . ivy--regex-ignore-order)))\n#+end_src\n\nDo not show ~./~ and ~../~ during file name completion.\n#+name: ivy-config\n#+begin_src emacs-lisp :tangle no\n(setq-default ivy-extra-directories nil)\n#+end_src\n\nThe normal =C-j= is not placed conveniently on Workman layout, so move its function to =C-e= (which is qwerty =k=).\n#+name: ivy-config\n#+begin_src emacs-lisp :tangle no\n(general-def 'ivy-minibuffer-map\n  \"C-e\"   #'ivy-alt-done\n  \"C-M-e\" #'ivy-immediate-done)\n#+end_src\n\nEvilify ivy-occur.\n#+name: ivy-config\n#+begin_src emacs-lisp :tangle no\n(with-eval-after-load \"evil\"\n  (general-def\n    :keymaps '(ivy-occur-mode-map ivy-occur-grep-mode-map)\n    :states 'normal\n    \"k\"    #'ivy-occur-next-line\n    \"j\"    #'ivy-occur-previous-line\n    \"C-n\"  #'ivy-occur-next-line\n    \"C-p\"  #'ivy-occur-previous-line\n    \"RET\"  #'ivy-occur-press-and-switch\n    \"TAB\"  #'ivy-occur-press\n    \"C-e\"  #'ivy-occur-press-and-switch\n    \"g r\"  #'ivy-occur-revert-buffer\n    \"g g\"  #'evil-goto-first-line\n    \"d\"    #'ivy-occur-delete-candidate\n    \"r\"    #'read-only-mode\n    \"a\"    #'ivy-occur-read-action\n    \"c\"    #'ivy-occur-toggle-calling\n    \"f\"    #'ivy-occur-press\n    \"o\"    #'ivy-occur-dispatch\n    \"q\"    #'quit-window)\n\n  (general-def 'normal 'ivy-occur-grep-mode-map\n    \"w\"    #'ivy-wgrep-change-to-wgrep-mode))\n#+end_src\n\n** smex\nI use smex for improved =counsel-M-x= (show most frequently used commands first).\n#+begin_src emacs-lisp\n(use-package smex\n  :config\n  :disabled t\n  (smex-initialize))\n#+end_src\n** counsel\n#+begin_src emacs-lisp\n(use-package counsel\n  :demand\n  :disabled t\n  :diminish counsel-mode\n  :general\n  (s-leader-def\n    \"x\"  #'counsel-M-x\n    \"f\"  #'counsel-find-file)\n  ('motion\n   \"g r\"    #'counsel-git-grep\n   \"g /\"    #'counsel-rg)\n  ('read-expression-map\n   \"C-r\"    #'counsel-expression-history)\n  :config\n  ;; reset ivy initial inputs for counsel\n  (setq-default ivy-initial-inputs-alist nil)\n  (counsel-mode 1))\n#+end_src\n\nInstall ripgrep (rg) to user environment:\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = [ pkgs.ripgrep ];\n}\n#+end_src\n** avy\nJump anywhere with a few keystrokes in tree-like way.\n#+begin_src emacs-lisp\n(use-package avy\n  :general\n  ('motion\n   \"K\"  #'avy-goto-char)\n  :custom\n  ;; easy workman keys (excluding pinky)\n  (avy-keys '(?s ?h ?t ?n ?e ?o ?d ?r ?u ?p)))\n#+end_src\n** imenu / imenu-list\nUse imenu to jump to symbols in the current buffer.\n#+begin_src emacs-lisp\n(use-package imenu-list\n  :disabled t\n  :general\n  (:keymaps 'imenu-list-major-mode-map\n   :states 'normal\n   \"RET\"       #'imenu-list-goto-entry\n   \"TAB\"       #'imenu-list-display-entry\n   \"\u003cbacktab\u003e\" #'hs-toggle-hiding\n   \"g r\"       #'imenu-list-refresh\n   \"q\"         #'imenu-list-quit-window)\n\n  (defun rasen/imenu-or-list (arg)\n    \"Invoke `counsel-imenu'. If prefix is provided, toggle imenu-list\"\n    (interactive \"P\")\n    (if arg\n        (imenu-list-smart-toggle)\n      (counsel-imenu)))\n\n  (leader-def 'motion  \"g\" #'rasen/imenu-or-list))\n#+end_src\n** wgrep\nEdit grep buffers and apply changes to the files.\n#+begin_src emacs-lisp\n(use-package wgrep)\n#+end_src\n** whitespace\nA good mode to highlight whitespace issues (leading/trailing spaces/newlines) and too long lines.\n#+begin_src emacs-lisp\n(use-package whitespace\n  :diminish (global-whitespace-mode\n             whitespace-mode\n             whitespace-newline-mode)\n  ;; :hook (prog-mode . whitespace-mode)\n  :config\n  (setq-default whitespace-line-column 120\n                whitespace-style '(face\n                                   tab-mark\n                                   empty\n                                   trailing\n                                   lines-tail))\n  (global-whitespace-mode))\n#+end_src\n** whitespace-cleanup\nFix whitespaces on file save.\n#+begin_src emacs-lisp\n(use-package whitespace-cleanup-mode\n  :diminish whitespace-cleanup-mode\n  :config\n  (global-whitespace-cleanup-mode 1))\n#+end_src\n** which-key\n[[https://github.com/justbur/emacs-which-key][which-key]] is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup.\n#+begin_src emacs-lisp\n(use-package which-key\n  :disabled t\n  :defer 2\n  :diminish which-key-mode\n  :config\n  (which-key-mode))\n#+end_src\n** Google translate\n#+begin_src emacs-lisp\n(use-package google-translate\n  :disabled t\n  :general\n  ('normal\n   '(markdown-mode-map org-mode-map)\n   \"z g t\" #'rasen/google-translate-at-point\n   \"z g T\" #'google-translate-smooth-translate)\n\n  :commands (google-translate-smooth-translate)\n  :config\n  (defun rasen/google-translate-at-point (arg)\n    \"Translate word at point. If prefix is provided, do reverse translation\"\n    (interactive \"P\")\n    (if arg\n        (google-translate-at-point-reverse)\n      (google-translate-at-point)))\n\n  (require 'google-translate-default-ui)\n  (require 'google-translate-smooth-ui)\n  (setq google-translate-show-phonetic t)\n\n  (setq google-translate-default-source-language \"en\"\n        google-translate-default-target-language \"ua\")\n\n  (setq google-translate-translation-directions-alist '((\"en\" . \"ua\") (\"ua\" . \"en\")))\n  ;; auto-toggle input method\n  (setq google-translate-input-method-auto-toggling t\n        google-translate-preferable-input-methods-alist '((nil . (\"en\"))\n                                                          (ukrainian-computer . (\"ua\")))))\n#+end_src\n** tab-bar-mode\nNew in Emacs 27.\n#+begin_src emacs-lisp\n(use-package tab-bar\n  :general\n  ('motion\n   \"M-h\"  #'tab-bar-switch-to-prev-tab\n   \"M-l\"  #'tab-bar-switch-to-next-tab)\n  :config\n  (general-def 'normal\n    \"M-h\"  #'tab-bar-switch-to-prev-tab\n    \"M-l\"  #'tab-bar-switch-to-next-tab)\n  (general-def '(normal visual) 'org-mode-map\n    \"M-h\" nil\n    \"M-l\" nil)\n  (general-def '(normal visual) 'evil-org-mode-map\n    \"M-h\" nil\n    \"M-l\" nil)\n  (general-def 'org-mode-map\n    \"M-h\" nil\n    \"M-l\" nil)\n\n  (setq tab-bar-select-tab-modifiers '(meta))\n  (setq tab-bar-tab-hints t)\n\n  ;; Show tab bar only if there are \u003e1 tab\n  (setq tab-bar-show 1)\n  ;; Do not show buttons\n  (setq tab-bar-close-button-show nil\n        tab-bar-new-button-show nil)\n  ;; (tab-bar-mode)\n  )\n#+end_src\n** Highlight current line\nHighlight current line.\n#+begin_src emacs-lisp\n(use-package emacs\n  :config\n  (global-hl-line-mode)\n\n  ;; The following trick with buffer-local `global-hl-line-mode` allows\n  ;; disabling hl-line-mode per-buffer\n  (make-variable-buffer-local 'global-hl-line-mode)\n  (defun rasen/disable-hl-line-mode ()\n    (interactive)\n    (setq global-hl-line-mode nil)))\n#+end_src\n** Scrolling\n=scroll-margin= is a number of lines of margin at the top and bottom of a window. Scroll the window whenever point gets within this many lines of the top or bottom of the window. (=scroll-conservatively= should be greater than 100 to never recenter point. Value 1 helps, but eventually recenters cursor if you scroll too fast.)\n#+begin_src emacs-lisp\n(setq scroll-margin 3\n      scroll-conservatively 101)\n#+end_src\n** visual-fill-column\nCenter all text in the buffer in some modes. (That's a nice distraction-free setup.)\n#+begin_src emacs-lisp\n(use-package visual-fill-column\n  :commands (visual-fill-column-mode)\n  :hook\n  (markdown-mode . rasen/activate-visual-fill-column)\n  (org-mode . rasen/activate-visual-fill-column)\n  :init\n  (defun rasen/activate-visual-fill-column ()\n    (interactive)\n    (setq-local fill-column 111)\n    (visual-line-mode t)\n    (visual-fill-column-mode t))\n  :config\n  (setq-default visual-fill-column-center-text t\n                visual-fill-column-fringes-outside-margins nil))\n#+end_src\n** Misc\nUse single-key =y/n= instead of a more verbose =yes/no=.\n#+begin_src emacs-lisp\n(fset 'yes-or-no-p 'y-or-n-p)\n#+end_src\n\nAutomatically add a final newline in files.\n#+begin_src emacs-lisp\n(setq-default require-final-newline t)\n#+end_src\n* Environment\n** EXWM\nEmacs is my Window Manager, thanks to [[https://github.com/ch11ng/exwm][EXWM]].\n\nNixOS has an EXWM module, but my feeling is that it's too limiting. (~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  environment.systemPackages = [ pkgs.xorg.xhost ];\n  services.xserver.windowManager.session = lib.singleton {\n    name = \"exwm\";\n    start = ''\n      xhost +SI:localuser:$USER\n      exec emacs\n    '';\n    # exec ${pkgs.my-emacs}/bin/emacsclient -a \"\" -c\n  };\n  services.xserver.displayManager.lightdm.enable = true;\n  # services.xserver.displayManager.startx.enable = true;\n  services.xserver.displayManager.defaultSession = \"none+exwm\";\n}\n#+end_src\n\nInitialize EXWM configuration (emacs-lisp)\n#+begin_src emacs-lisp\n(use-package exwm\n  :init\n  ;; these must be set before exwm is loaded\n  (setq mouse-autoselect-window t\n        focus-follows-mouse t)\n  :config\n  ;; the next two make all buffers available on all workspaces\n  (setq exwm-workspace-show-all-buffers t)\n  (setq exwm-layout-show-all-buffers t)\n\n  ;; Make class name the buffer name\n  (add-hook 'exwm-update-class-hook\n            (lambda ()\n              (exwm-workspace-rename-buffer exwm-class-name)))\n\n  (with-eval-after-load 'evil\n    (evil-set-initial-state 'exwm-mode 'motion))\n\n  ;; do not forward anything besides keys defined with\n  ;; `exwm-input-set-key' and `exwm-mode-map'\n  (setq exwm-input-prefix-keys '())\n\n  (exwm-enable))\n#+end_src\n\nAdd a couple of helpers functions. (emacs-lisp)\n#+begin_src emacs-lisp\n(defun rasen/autostart (cmd)\n  \"Start CMD unless already running.\"\n  (let ((buf-name (concat \"*\" cmd \"*\")))\n    (unless (process-live-p (get-buffer-process buf-name))\n      (start-process-shell-command cmd buf-name cmd))))\n\n(defun rasen/start-command (command \u0026optional buffer)\n  \"Start shell COMMAND in the background. If BUFFER is provided, log process output to that buffer.\"\n  (interactive (list (read-shell-command \"Run: \")))\n  (start-process-shell-command command buffer command))\n\n(defun rasen/switch-start (buffer cmd)\n  \"Switch to buffer with name BUFFER or start one with CMD.\"\n  (if-let (b (get-buffer buffer))\n      (switch-to-buffer b)\n    (rasen/start-command cmd)))\n\n(defun rasen/exwm-input-set-key (key command)\n  \"Similar to `exwm-input-set-key', but always refreshes prefix\nkeys. This allows defining keys from any place in config.\"\n  (exwm-input-set-key key command)\n  ;; Alternatively, try general-setq (which calls customize handler)\n  (exwm-input--update-global-prefix-keys))\n#+end_src\n** Window management\nCommon key bindings. (emacs-lisp)\n#+begin_src emacs-lisp\n(use-package evil\n  :disabled t\n  :defer t\n  :commands (evil-window-split\n             evil-window-vsplit))\n\n(defun rasen/exwm-next-workspace ()\n  (interactive)\n  ;; (let ((cur exwm-workspace-current-index)\n  ;;       (max exwm-workspace-number))\n  ;;   (exwm-workspace-switch (% (+ cur 1) max)))\n  (other-frame 1))\n\n(defun rasen/move-tab-other-frame ()\n  (interactive)\n  (tab-bar-move-tab-to-frame nil))\n\n;; despite the fact s-SPC binds to nil, EXWM will add s-SPC to\n;; global prefix key.\n(exwm-input-set-key (kbd \"s-SPC\") nil)\n(exwm-input-set-key (kbd \"s-x\") #'execute-extended-command)\n\n(exwm-input-set-key (kbd \"s-R\") #'exwm-reset)\n(exwm-input-set-key (kbd \"s-Q\") #'save-buffers-kill-terminal)\n(exwm-input-set-key (kbd \"s-r\") (lambda (command)\n                                  (interactive (list (read-shell-command \"Run: \")))\n                                  (rasen/start-command command)))\n(exwm-input-set-key (kbd \"s-w\") #'exwm-workspace-switch)\n(exwm-input-set-key (kbd \"s-b\") #'switch-to-buffer)\n(exwm-input-set-key (kbd \"s-q\") #'kill-this-buffer)\n\n(exwm-input-set-key (kbd \"s-\\\\\") #'exwm-input-toggle-keyboard)\n(exwm-input-set-key (kbd \"\u003cs-escape\u003e\") #'rasen/switch-to-previous-buffer)\n(exwm-input-set-key (kbd \"\u003cs-tab\u003e\") #'counsel-switch-buffer)\n\n;; window management\n(exwm-input-set-key (kbd \"s--\") #'delete-other-windows)\n(exwm-input-set-key (kbd \"s-0\") #'delete-window)\n(exwm-input-set-key (kbd \"s-n\") #'yabai-windmove-left)\n(exwm-input-set-key (kbd \"s-e\") #'yabai-windmove-down)\n(exwm-input-set-key (kbd \"s-u\") #'yabai-windmove-up)\n(exwm-input-set-key (kbd \"s-o\") #'yabai-windmove-right)\n(exwm-input-set-key (kbd \"s-h\") (rasen/hard-way \"s-n\"))\n(exwm-input-set-key (kbd \"s-l\") (rasen/hard-way \"s-o\"))\n(exwm-input-set-key (kbd \"s-j\") (rasen/hard-way \"s-u\"))\n(exwm-input-set-key (kbd \"s-k\") (rasen/hard-way \"s-e\"))\n\n(exwm-input-set-key (kbd \"s-s\") #'split-window-below)\n(exwm-input-set-key (kbd \"s-v\") #'split-window-right)\n\n(exwm-input-set-key (kbd \"s-.\") #'rasen/exwm-next-workspace)\n(exwm-input-set-key (kbd \"s-\u003e\") #'rasen/move-tab-other-frame) ;; s-S-.\n\n(general-def\n  :prefix-command 'rasen/tab-map\n  \"T\" #'tab-bar-mode\n  \"1\" #'tab-bar-select-tab\n  \"2\" #'tab-bar-select-tab\n  \"3\" #'tab-bar-select-tab\n  \"4\" #'tab-bar-select-tab\n  \"5\" #'tab-bar-select-tab\n  \"6\" #'tab-bar-select-tab\n  \"7\" #'tab-bar-select-tab\n  \"8\" #'tab-bar-select-tab\n  \"9\" #'tab-bar-select-tab\n  \"t\" #'tab-bar-new-tab\n  \"n\" #'tab-bar-switch-to-prev-tab\n  \"o\" #'tab-bar-switch-to-next-tab\n  \"\u003e\" #'tab-bar-move-tab-to-frame\n  \"k\" #'tab-bar-close-tab)\n\n(exwm-input-set-key (kbd \"s-t\") #'rasen/tab-map)\n\n(exwm-input-set-key (kbd \"s-1\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-2\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-3\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-4\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-5\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-6\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-7\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-8\") #'tab-bar-select-tab)\n(exwm-input-set-key (kbd \"s-9\") #'tab-bar-select-tab)\n\n(exwm-input-set-key (kbd \"\u003cs-f1\u003e\") (lookup-key (current-global-map) (kbd \"\u003cf1\u003e\")))\n\n(defun rasen/exwm-firefox ()\n  (interactive)\n  (rasen/switch-start \"Firefox\" \"firefox\"))\n\n(defun rasen/exwm-telegram ()\n  (interactive)\n  (rasen/switch-start \"TelegramDesktop\" \"telegram-desktop\"))\n\n(defun rasen/exwm-google-play-music ()\n  (interactive)\n  (rasen/switch-start \"Google Play Music Desktop Player\" \"google-play-music-desktop-player\"))\n\n(defun rasen/terminal ()\n  (interactive)\n  (rasen/start-command \"urxvt\"))\n\n;; From https://emacsredux.com/blog/2013/04/28/switch-to-previous-buffer/\n(defun rasen/switch-to-previous-buffer ()\n  \"Switch to previously open buffer.\n  Repeated invocations toggle between the two most recently open buffers.\"\n  (interactive)\n  (switch-to-buffer (other-buffer (current-buffer))))\n\n(exwm-input-set-key (kbd \"s-!\") #'rasen/exwm-firefox)           ;; s-S-1\n(exwm-input-set-key (kbd \"s-$\") #'rasen/exwm-telegram)          ;; s-S-4\n(exwm-input-set-key (kbd \"s-\u0026\") #'rasen/exwm-google-play-music) ;; s-S-7\n(exwm-input-set-key (kbd \"s-(\") #'notmuch)                      ;; s-S-9\n(exwm-input-set-key (kbd \"\u003cs-return\u003e\") #'vterm)\n(exwm-input-set-key (kbd \"\u003cs-S-return\u003e\") #'rasen/terminal)\n\n(exwm-input-set-key (kbd \"s-z\") #'exwm-layout-toggle-mode-line)\n(exwm-input-set-key (kbd \"s-f\") #'exwm-layout-toggle-fullscreen)\n(exwm-input-set-key (kbd \"s-C-SPC\") #'exwm-floating-toggle-floating)\n\n(general-def 'exwm-mode-map\n  \"C-c\" nil ;; disable default bindings\n\n  \"\u003cf1\u003e v\" #'counsel-describe-variable)\n\n;; Without the next line, EXWM won't intercept necessary prefix keys\n;; (if you rebind them after EXWM has started)\n(exwm-input--update-global-prefix-keys)\n#+end_src\n** Window layout\nRules to automatically layout windows when they appear.\n#+begin_src emacs-lisp\n(setq display-buffer-alist\n      '((\"\\\\*\\\\(Help\\\\|Error\\\\)\\\\*\" .\n         (display-buffer-in-side-window\n          (side . right)\n          (slot . 1)\n          (window-width . 80)\n          (no-other-window . t)))\n        (\"\\\\*\\\\(Calendar\\\\)\\\\*\" .\n         (display-buffer-in-side-window\n          (side . bottom)\n          (slot . -1)\n          ;; (window-width . 80)\n          (no-other-window . t)))\n        (\"\\\\*org-roam\\\\*\" .\n         (display-buffer-in-side-window\n          (side . right)\n          (slot . -1)\n          (window-width . 80)\n          (no-other-window . t)))))\n#+end_src\n** Screen locking\nI use ~xss-lock~ + ~slock~ for screen locking. Actual handling is coded in Emacs.\n*** Slock\n[[http://tools.suckless.org/slock/][Slock]] is a simple X display locker and does not crash as xscreensaver does.\n\nSlock tries to disable OOM killer (so the locker is not killed when memory is low) and this requires a suid flag for executable. Otherwise, you get the following message:\n#+begin_src fundamental :tangle no\nslock: unable to disable OOM killer. Make sure to suid or sgid slock.\n#+end_src\n\n#+name: nixos-section\n#+begin_src nix\n{\n  programs.slock.enable = true;\n}\n#+end_src\n*** xss-lock\n[[https://bitbucket.org/raymonad/xss-lock][xss-lock]] is a small utility to plug a screen locker into screen saver extension for X. This automatically activates selected screensaver after a period of user inactivity, or when system goes to sleep.\n\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = pkgs.lib.linux-only [\n    pkgs.xss-lock\n  ];\n}\n#+end_src\n*** EXWM integration\nAutostart ~xss-lock~ (emacs-lisp).\n#+begin_src emacs-lisp\n(when (eq system-type 'gnu/linux)\n  (rasen/autostart \"xss-lock -n \\\"xset dpms force off\\\" slock\"))\n#+end_src\n\nBind ~s-M-l~ to lock screen immediately.\n#+begin_src emacs-lisp\n(defun rasen/blank-screen ()\n  \"Blank screen after 1 second. The delay is introduced so the user\ncould get their hands away from the keyboard. Otherwise, the screen\nwould lit again immediately.\"\n  (interactive)\n  (run-at-time \"1 sec\" nil\n               (lambda ()\n                 (rasen/start-command \"xset dpms force off\"))))\n\n(defun rasen/lock-screen ()\n  \"Lock and blank screen.\"\n  (interactive)\n  (rasen/start-command \"slock\")\n  (rasen/blank-screen))\n\n(rasen/exwm-input-set-key (kbd \"s-M-l\") #'rasen/lock-screen)\n#+end_src\n** System tray\nUse built-in EXWM system tray (emacs-lisp)\n#+begin_src emacs-lisp\n(use-package exwm-systemtray\n  :after exwm\n  :config\n  (exwm-systemtray-mode))\n#+end_src\n** Screenshots\nI use [[https://github.com/Roger/escrotum][Escrotum]] for screenshots.\n\nInstall it. (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = pkgs.lib.linux-only [ pkgs.escrotum ];\n}\n#+end_src\n\nBind it to Print Screen button. (emacs-lisp)\n#+begin_src emacs-lisp\n(defun rasen/screenshot ()\n  (interactive)\n  ;; -sC — choose selection + save to clipboard\n  (rasen/start-command \"escrotum -sC\"))\n\n(rasen/exwm-input-set-key (kbd \"\u003cprint\u003e\") #'rasen/screenshot)\n#+end_src\n** Misc\nI definitely use X server:\n#+name: nixos-section\n#+begin_src nix\n{\n  services.xserver.enable = true;\n}\n#+end_src\n\nUse English as my only supported locale:\n#+name: nixos-section\n#+begin_src nix\n{\n  i18n.supportedLocales = [ \"en_US.UTF-8/UTF-8\" ];\n}\n#+end_src\n\nSetup timezone:\n#+name: nixos-section\n#+begin_src nix\n{\n  time.timeZone = \"Europe/Kiev\";\n}\n#+end_src\n* Input\n** Keyboard\n*** Workman\nI use [[https://workmanlayout.org/][Workman Layout]].\nIt's a nice non-qwerty layout that de-prioritizes two middle /columns,/ so your hands don't rotate too often.\n\nIt looks like this:\n#+DOWNLOADED: https://nic-west.com/images/workman.png @ 2020-06-13 02:39:31\n[[file:images/20200612233931-workman.png]]\n\n*** Keyboard layout\nBesides Workman, I use Ukrainian layout. I also use Russian symbols, but they are on the third level (~\u003c\u003cnixos-section\u003e\u003e~).\n#+name: nixos-section\n#+begin_src nix\n{\n  services.xserver.layout = \"us,ua\";\n  services.xserver.xkbVariant = \"workman,\";\n\n  # Use same config for linux console\n  console.useXkbConfig = true;\n}\n#+end_src\n\nSame setting but for Home Manager (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.keyboard = {\n    layout = \"us,ua\";\n    variant = \"workman,\";\n  };\n}\n#+end_src\n\nMap left Caps Lock to Ctrl, and left Ctrl to switch between layout. (Shift-Ctrl triggers Caps Lock function.)\nI never use Caps Lock–the feature, so it's nice to have Caps LED indicate alternate layouts.\n\n~\u003c\u003cnixos-section\u003e\u003e~:\n#+name: nixos-section\n#+begin_src nix\n{\n  services.xserver.xkbOptions = \"grp:lctrl_toggle,grp_led:caps,ctrl:nocaps\";\n  # services.xserver.xkbOptions = \"grp:caps_toggle,grp_led:caps\";\n}\n#+end_src\n\nOn macOS, right option acts as AltGr. Make Emacs ignore it, so it can work correctly as a Meta modifier:\n#+begin_src emacs-lisp\n(setq ns-right-alternate-modifier 'none)\n#+end_src\n\n*** Xkeymap\nI have a slightly customized Workman+Ukrainian layout at [[./Xkeymap]] (more keys on 3rd level). It's quite big and isn't particularly fun to explain, so I keep it off my main config.\n\nActivate it on session start (~\u003c\u003chome-manager-section\u003e\u003e~).\n#+name: home-manager-section\n#+begin_src nix\n{\n  xsession.initExtra = ''\n    xkbcomp ${./Xkeymap} $DISPLAY\n  '';\n}\n#+end_src\n\nOne caveat is that it's dropped when I activate (update) new system version, or when unplug keyboard and plug it again.\n\nAdd a small Emacs function to re-apply this configuration (emacs-lisp).\n#+begin_src emacs-lisp\n(defun rasen/set-xkb-layout ()\n  (interactive)\n  (rasen/autostart \"xkbcomp ~/dotfiles/Xkeymap $DISPLAY\"))\n\n(rasen/set-xkb-layout)\n#+end_src\n\nInstall xkbcomp to execute these commands. (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = [ pkgs.xorg.xkbcomp ];\n}\n#+end_src\n\n*** Compose keys\nAdd some custom compose keys (~\u003c\u003chome-manager-section\u003e\u003e~):\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.file.\".XCompose\".text = ''\n    include \"%L\"\n\n    \u003cMulti_key\u003e \u003cless\u003e \u003cequal\u003e           : \"⇐\" U21D0 # Leftwards Double Arrow\n    \u003cMulti_key\u003e \u003cequal\u003e \u003cgreater\u003e        : \"⇒\" U21D2 # RIGHTWARDS DOUBLE ARROW\n    \u003cMulti_key\u003e \u003cless\u003e \u003cgreater\u003e \u003cequal\u003e : \"⇔\" U21D4 # LEFT RIGHT DOUBLE ARROW\n    \u003cMulti_key\u003e \u003cequal\u003e \u003cless\u003e \u003cgreater\u003e : \"⇔\" U21D4 # LEFT RIGHT DOUBLE ARROW\n    \u003cMulti_key\u003e \u003cminus\u003e \u003cless\u003e \u003cgreater\u003e : \"↔\" U2194 # LEFT RIGHT ARROW\n\n    \u003cMulti_key\u003e \u003cs\u003e \u003cu\u003e \u003cm\u003e : \"∑\"\n    \u003cMulti_key\u003e \u003cf\u003e \u003ca\u003e : \"∀\"                 # for all\n    \u003cMulti_key\u003e \u003ct\u003e \u003ce\u003e : \"∃\"                 # there exists\n    \u003cMulti_key\u003e \u003cslash\u003e \u003ct\u003e \u003ce\u003e : \"∄\"\n    \u003cMulti_key\u003e \u003casciitilde\u003e \u003cequal\u003e : \"≅\"    # approximately equal\n    \u003cMulti_key\u003e \u003casciitilde\u003e \u003casciitilde\u003e : \"≈\"   U2248   # ~ ~ ALMOST EQUAL TO\n    \u003cMulti_key\u003e \u003ci\u003e \u003cn\u003e : \"∈\" U2208\n    \u003cMulti_key\u003e \u003cn\u003e \u003ci\u003e \u003cn\u003e : \"∉\" U2209\n\n    # White Right Pointing Index\n    \u003cMulti_key\u003e \u003crght\u003e : \"☞\" U261E\n\n    \u003cMulti_key\u003e \u003co\u003e \u003cc\u003e : \"℃\"\n    \u003cMulti_key\u003e \u003co\u003e \u003cf\u003e : \"℉\"\n\n    \u003cMulti_key\u003e \u003cx\u003e \u003cx\u003e : \"❌\"  # Cross Mark\n\n    \u003cMulti_key\u003e \u003capostrophe\u003e \u003capostrophe\u003e : \"́\" # stress\n\n    \u003cMulti_key\u003e \u003cO\u003e \u003cslash\u003e : \"⌀\" U2300 # DIAMETER SIGN\n    \u003cMulti_key\u003e \u003cslash\u003e \u003cO\u003e : \"⌀\" U2300 # DIAMETER SIGN\n    \u003cMulti_key\u003e \u003cr\u003e \u003cr\u003e : \"√\" U221A # SQUARE ROOT\n    \u003cMulti_key\u003e \u003cr\u003e \u003c3\u003e : \"∛\" U221B # CUBE ROOT\n    \u003cMulti_key\u003e \u003cm\u003e \u003cA\u003e : \"∀\" U2200 # FOR ALL\n    \u003cMulti_key\u003e \u003cm\u003e \u003cE\u003e : \"∃\" U2203 # THERE EXISTS\n    \u003cMulti_key\u003e \u003cm\u003e \u003ci\u003e : \"∊\" U220A # SMALL ELEMENT OF\n    \u003cMulti_key\u003e \u003cm\u003e \u003cd\u003e : \"∂\" U2202 # PARTIAL DIFFERENTIAL\n    \u003cMulti_key\u003e \u003cm\u003e \u003cD\u003e : \"∆\" U2206 # INCREMENT, Laplace operator\n    \u003cMulti_key\u003e \u003cm\u003e \u003cS\u003e : \"∑\" U2211 # N-ARY SUMMATION, Sigma\n    \u003cMulti_key\u003e \u003cm\u003e \u003cI\u003e : \"∫\" U222B # INTEGRAL\n    \u003cMulti_key\u003e \u003cm\u003e \u003cminus\u003e : \"−\" U2212 # MINUS SIGN\n    \u003cMulti_key\u003e \u003cequal\u003e \u003casciitilde\u003e : \"≈\" U2248 # ALMOST EQUAL TO\n    \u003cMulti_key\u003e \u003casciitilde\u003e \u003cequal\u003e : \"≈\" U2248 # ALMOST EQUAL TO\n    \u003cMulti_key\u003e \u003cunderscore\u003e \u003cunderscore\u003e : \"‾\" U023E # OVERLINE\n    \u003cMulti_key\u003e \u003cequal\u003e \u003cslash\u003e  \t: \"≠\"   U2260 # NOT EQUAL TO\n    \u003cMulti_key\u003e \u003cslash\u003e \u003cequal\u003e  \t: \"≠\"   U2260 # NOT EQUAL TO\n    \u003cMulti_key\u003e \u003cminus\u003e \u003cequal\u003e \t: \"≡\"   U2261 # IDENTICAL TO\n    \u003cMulti_key\u003e \u003cequal\u003e \u003cminus\u003e \t: \"≡\"   U2261 # IDENTICAL TO\n    \u003cMulti_key\u003e \u003cm\u003e \u003cless\u003e \u003cequal\u003e : \"≤\" U2264 # LESS-THAN OR EQUAL TO\n    \u003cMulti_key\u003e \u003cm\u003e \u003cgreater\u003e \u003cequal\u003e : \"≥\" U2265 # GREATER-THAN OR EQUAL TO\n    \u003cMulti_key\u003e \u003cm\u003e \u003co\u003e \u003co\u003e : \"∞\" # infty\n    \u003cMulti_key\u003e \u003cm\u003e \u003c_\u003e \u003ci\u003e : \"ᵢ\" # subscript i\n    \u003cMulti_key\u003e \u003cm\u003e \u003c^\u003e \u003ci\u003e : \"ⁱ\" # superscript i\n    \u003cMulti_key\u003e \u003cm\u003e \u003c_\u003e \u003cminus\u003e : \"₋\" # subscript minus\n    \u003cMulti_key\u003e \u003cm\u003e \u003c^\u003e \u003cminus\u003e : \"⁻\" # superscript minus\n    \u003cMulti_key\u003e \u003cm\u003e \u003c_\u003e \u003cplus\u003e : \"₊\" # subscript plus\n    \u003cMulti_key\u003e \u003cm\u003e \u003c^\u003e \u003cplus\u003e : \"⁺\" # superscript plus\n    \u003cMulti_key\u003e \u003cm\u003e \u003casterisk\u003e : \"∘\" # ring (function compose) operator\n    \u003cMulti_key\u003e \u003cm\u003e \u003cperiod\u003e : \"∙\" # dot operator\n    \u003cMulti_key\u003e \u003cm\u003e \u003casciitilde\u003e : \"∝\" # proportional to\n    \u003cMulti_key\u003e \u003cq\u003e \u003ce\u003e \u003cd\u003e : \"∎\" # q.e.d.\n  '';\n}\n#+end_src\n\n*** xcape\nMake short press on left control behave as Escape (=\u003c\u003chome-manager-section\u003e\u003e=):\n#+name: home-manager-section\n#+begin_src nix\n{\n  services.xcape = {\n    enable = pkgs.stdenv.isLinux;\n    mapExpression = {\n      Control_L = \"Escape\";\n    };\n  };\n}\n#+end_src\n*** Emacs quail\nEmacs has built-in capability to change keyboard layout (for insert state only), which is triggered by =C-\\=. In order to work properly, Emacs needs to know my keyboard layout.\n#+begin_src emacs-lisp\n(use-package quail\n  :ensure nil ; built-in\n  :config\n  (add-to-list 'quail-keyboard-layout-alist\n               '(\"workman\" . \"\\\n                              \\\n  1!2@3#4$5%6^7\u00268*9(0)-_=+`~  \\\n  qQdDrRwWbBjJfFuUpP;:[{]}\\\\|  \\\n  aAsShHtTgGyYnNeEoOiI'\\\"      \\\n  zZxXmMcCvVkKlL,\u003c.\u003e/?        \\\n                              \"))\n  (quail-set-keyboard-layout \"workman\"))\n#+end_src\n\n* Network\n** NetworkManager\n(~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  networking = {\n    hostName = name;\n\n    networkmanager = {\n      enable = true;\n      wifi.powersave = false;\n    };\n\n    # disable wpa_supplicant\n    wireless.enable = false;\n  };\n\n  users.extraUsers.rasen.extraGroups = [ \"networkmanager\" ];\n}\n#+end_src\n\nInstall network manager applet for user. (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = pkgs.lib.linux-only [ pkgs.networkmanagerapplet ];\n}\n#+end_src\n\nAuto-start ~nm-applet~ (emacs-lisp)\n#+begin_src emacs-lisp\n(rasen/autostart \"nm-applet\")\n#+end_src\n** SSH\n(~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  services.openssh = {\n    enable = true;\n    passwordAuthentication = false;\n  };\n}\n#+end_src\n*** Mosh\n[[https://mosh.mit.edu/][Mosh (mobile shell)]] is a cool addition to ssh.\n#+name: nixos-section\n#+begin_src nix\n{\n  programs.mosh.enable = true;\n}\n#+end_src\n** dnsmasq\nUse [[http://www.thekelleys.org.uk/dnsmasq/doc.html][dnsmasq]] as a DNS cache.\n\n(~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  services.dnsmasq = {\n    enable = true;\n\n    # These are used in addition to resolv.conf\n    servers = [\n      \"8.8.8.8\"\n      \"8.8.4.4\"\n    ];\n\n    extraConfig = ''\n      interface=lo\n      bind-interfaces\n      listen-address=127.0.0.1\n      cache-size=1000\n\n      no-negcache\n    '';\n  };\n}\n#+end_src\n** Firewall\nEnable firewall. This blocks all ports for ingress traffic and pings.\n\n(~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  networking.firewall = {\n    enable = true;\n    allowPing = false;\n\n    connectionTrackingModules = [];\n    autoLoadConntrackHelpers = false;\n  };\n}\n#+end_src\n* Services\n** Locate\nUpdate [[https://linux.die.net/man/1/locate][locate]] database daily.\n#+name: nixos-section\n#+begin_src nix\n{\n  services.locate = {\n    enable = true;\n    localuser = \"rasen\";\n  };\n}\n#+end_src\n** Gitolite\n#+name: nixos-section\n#+begin_src nix\n{\n  services.gitolite = {\n    enable = true;\n    user = \"git\";\n    adminPubkey = \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDHH15uiQw3jBbrdlcRb8wOr8KVltuwbHP/JOFAzXFO1l/4QxnKs6Nno939ugULM7Lu0Vx5g6FreuCOa2NMWk5rcjIwOzjrZnHZ7aoAVnE7H9scuz8NGnrWdc1Oq0hmcDxdZrdKdB6CPG/diGWNZy77nLvz5JcX1kPLZENPeApCERwR5SvLecA4Es5JORHz9ssEcf8I7VFpAebfQYDu+VZZvEu03P2+5SXv8+5zjiuxM7qxzqRmv0U8eftii9xgVNC7FaoRBhhM7yKkpbnqX7IeSU3WeVcw4+d1d8b9wD/sFOyGc1xAcvafLaGdgeCQGU729DupRRJokpw6bBRQGH29 rasen@omicron\";\n  };\n}\n#+end_src\n** Syncthing\nI use Syncthing to sync my org-mode files to my phone.\n\n#+name: nixos-section\n#+begin_src nix\n{\n  services.syncthing = {\n    enable = true;\n    user = \"rasen\";\n    dataDir = \"/home/rasen/.config/syncthing\";\n    configDir = \"/home/rasen/.config/syncthing\";\n    openDefaultPorts = true;\n  };\n}\n#+end_src\n\nOn Darwin:\n#+name: darwin-section\n#+begin_src nix\n{\n  environment.systemPackages = [ pkgs.syncthing ];\n}\n#+end_src\n** Backup\nI use borg for backups.\n\n#+name: machine-omicron\n#+begin_src nix\n(let\n  commonOptions = {\n    # repo = \"borg@10.13.0.3:.\";\n    repo = \"/run/media/ext-data/borg\";\n    removableDevice = true;\n\n    encryption.mode = \"keyfile-blake2\";\n    encryption.passCommand = \"cat /root/secrets/borg\";\n    compression = \"auto,lzma,9\";\n    doInit = false;\n    environment = { BORG_RSH = \"ssh -i /root/.ssh/borg\"; };\n    # UTC timestamp\n    dateFormat = \"-u +%Y-%m-%dT%H:%M:%S\";\n\n    prune.keep = {\n      daily = 7;\n      weekly = 4;\n      monthly = 12;\n      yearly = -1;\n    };\n  };\nin {\n  services.borgbackup.jobs.\"all\" = commonOptions // {\n    archiveBaseName = \"${config.networking.hostName}\";\n    paths = [\n      \"/var/lib/gitolite/\"\n      \"/home/rasen/backup/\"\n      \"/home/rasen/.ssh/\"\n      \"/home/rasen/.gnupg/\"\n      \"/home/rasen/.password-store/\"\n      \"/home/rasen/dotfiles/\"\n      \"/home/rasen/org/\"\n      \"/home/rasen/syncthing/\"\n\n      # Mail\n      \"/home/rasen/Mail/\"\n      \"/home/rasen/.mbsync/\"\n    ];\n    exclude = [\n      # Scanning notmuch takes too much time and doesn't make much\n      # sense as it is easily replicable\n      \"/home/rasen/Mail/.notmuch\"\n    ];\n  };\n\n  # Start backup on boot if missed one while laptop was off\n  systemd.timers.borgbackup-job-all.timerConfig = {\n    Persistent = lib.mkForce true;\n  };\n})\n#+end_src\n\nMount external drive when needed.\n#+name: nixos-section\n#+begin_src nix\n{\n  # Prepare mount point\n  system.activationScripts = {\n    ensure-ext-data = {\n      text = ''\n        mkdir -p /run/media/ext-data\n      '';\n      deps = [];\n    };\n  };\n\n  fileSystems.\"/run/media/ext-data\" = {\n    device = \"/dev/disk/by-uuid/63972645-dbc8-4543-b854-91038b2da6cb\";\n    fsType = \"ext4\";\n    options = [\n      \"noauto\"                       # do not mount on boot\n      \"nofail\"\n      \"x-systemd.automount\"          # mount when needed\n      \"x-systemd.device-timeout=1ms\" # device should be plugged already—do not wait for it\n      \"x-systemd.idle-timout=5m\"     # unmount after 5 min of inactivity\n    ];\n  };\n}\n#+end_src\n** direnv\n*** direnv + lorri\ndirenv allows having per-directory environment configuration. You can think of automatic virtualenv, but it's more general and supports unloading.\n\n(~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  programs.direnv.enable = true;\n  programs.direnv.nix-direnv.enable = true;\n  services.lorri.enable = pkgs.stdenv.isLinux;\n}\n#+end_src\n\n# Enable Emacs integration. (emacs-lisp)\n# #+begin_src emacs-lisp\n# (use-package direnv\n#   :after exec-path-from-shell\n#   :config\n#   (direnv-mode))\n# #+end_src\n\nBetter (?) direnv integration via [[https://github.com/purcell/envrc][purcell/envrc]]. (emacs-lisp)\n#+begin_src emacs-lisp\n(use-package envrc\n  :after exec-path-from-shell\n  :hook (after-init . envrc-global-mode))\n#+end_src\n** VirtualBox\n#+name: nixos-section\n#+begin_src nix\n{\n  virtualisation.virtualbox.host.enable = true;\n  users.extraGroups.vboxusers.members = [\"rasen\"];\n}\n#+end_src\n* Hardware\n** Do not suspend on AC\n#+name: nixos-section\n#+begin_src nix\n{\n  services.logind = {\n    lidSwitchDocked = \"ignore\";\n    lidSwitchExternalPower = \"ignore\";\n  };\n}\n#+end_src\n** Autorandr\nConfigure EXWM to use autorandr. (emacs-lisp)\n#+begin_src emacs-lisp\n(use-package exwm-randr\n  :after exwm\n  :config\n  (setq exwm-workspace-number 2)\n  (setq exwm-randr-workspace-output-plist '(0 \"eDP-1\"\n                                              1 \"DP-1\"\n                                              1 \"DP-3\"))\n\n  (add-hook 'exwm-randr-screen-change-hook\n            (defun rasen/autorandr ()\n              (interactive)\n              (rasen/start-command \"autorandr -c\" \"*autorandr*\")))\n\n  (exwm-randr-mode))\n#+end_src\n\n(~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  programs.autorandr = {\n    enable = true;\n    profiles =\n      let\n        omicron = \"00ffffffffffff004d104a14000000001e190104a51d11780ede50a3544c99260f505400000001010101010101010101010101010101cd9180a0c00834703020350026a510000018a47480a0c00834703020350026a510000018000000fe0052584e3439814c513133335a31000000000002410328001200000b010a202000cc\";\n        work = \"00ffffffffffff004d108d1400000000051c0104a52213780ea0f9a95335bd240c5157000000010101010101010101010101010101014dd000a0f0703e803020350058c210000018000000000000000000000000000000000000000000fe00464e564452804c513135364431000000000002410328011200000b010a202000ee\";\n        home-monitor = \"00ffffffffffff0010acc0a042524530031c010380351e78eae245a8554da3260b5054a54b00714f8180a9c0a940d1c0e10001010101a36600a0f0701f80302035000f282100001a000000ff004438565846383148304552420a000000fc0044454c4c205032343135510a20000000fd001d4c1e8c1e000a202020202020018802032ef15390050402071601141f1213272021220306111523091f07830100006d030c001000003c200060030201023a801871382d40582c25000f282100001e011d8018711c1620582c25000f282100009e04740030f2705a80b0588a000f282100001e565e00a0a0a02950302035000f282100001a0000000000000000008a\";\n        home-monitor-2 = \"00ffffffffffff004c2d767135305943341f0103804024782a6115ad5045a4260e5054bfef80714f810081c081809500a9c0b300010108e80030f2705a80b0588a0078682100001e000000fd0030901eff8f000a202020202020000000fc004c53323841473730304e0a2020000000ff0048345a524330303236380a2020017f02034bf14761103f04035f762309070783010000e305c0006b030c001000b83c200020016dd85dc401788053003090c354056d1a0000020f3090000461045a04e6060501615a00e30f4100565e00a0a0a029503020350078682100001a6fc200a0a0a055503020350078682100001a0000000000000000000000000000000037\";\n        work-monitor = \"00ffffffffffff0010acc2d0545741312c1b010380351e78eaad75a9544d9d260f5054a54b008100b300d100714fa9408180d1c00101565e00a0a0a02950302035000e282100001a000000ff004d59334e44374234314157540a000000fc0044454c4c205032343138440a20000000fd0031561d711c000a202020202020010302031bb15090050403020716010611121513141f2065030c001000023a801871382d40582c45000e282100001e011d8018711c1620582c25000e282100009ebf1600a08038134030203a000e282100001a7e3900a080381f4030203a000e282100001a00000000000000000000000000000000000000000000000000000000d8\";\n      in {\n        \"omicron\" = {\n          fingerprint = {\n            eDP-1 = omicron;\n          };\n          config = {\n            eDP-1 = {\n              enable = true;\n              primary = true;\n              position = \"0x0\";\n              mode = \"3200x1800\";\n              rate = \"60.00\";\n            };\n          };\n        };\n        \"omicron-home\" = {\n          fingerprint = {\n            eDP-1 = omicron;\n            DP-1 = home-monitor;\n          };\n          config = {\n            eDP-1.enable = false;\n            DP-1 = {\n              enable = true;\n              primary = true;\n              position = \"0x0\";\n              mode = \"3840x2160\";\n              rate = \"60.00\";\n            };\n          };\n        };\n        \"omicron-home-2\" = {\n          fingerprint = {\n            eDP-1 = omicron;\n            DP-1 = home-monitor-2;\n          };\n          config = {\n            eDP-1.enable = false;\n            DP-1 = {\n              enable = true;\n              primary = true;\n              position = \"0x0\";\n              mode = \"3840x2160\";\n              rate = \"60.00\";\n            };\n          };\n        };\n        \"omicron-home-monitor\" = {\n          fingerprint = {\n            DP-1 = home-monitor;\n          };\n          config = {\n            DP-1 = {\n              enable = true;\n              primary = true;\n              position = \"0x0\";\n              mode = \"3840x2160\";\n              rate = \"60.00\";\n            };\n          };\n        };\n        omicron-home-monitor-2 = {\n          fingerprint = {\n            DP-1 = home-monitor-2;\n          };\n          config = {\n            DP-1 = {\n              enable = true;\n              primary = true;\n              position = \"0x0\";\n              mode = \"3840x2160\";\n              rate = \"60.00\";\n            };\n          };\n        };\n      };\n  };\n}\n#+end_src\n** Screen brightness\n=xbacklight= stopped working recently. =acpilight= is a drop-in replacement.\n#+name: nixos-section\n#+begin_src nix\n{\n  hardware.acpilight.enable = true;\n  environment.systemPackages = [ pkgs.acpilight ];\n  users.extraUsers.rasen.extraGroups = [ \"video\" ];\n}\n#+end_src\n\nFor Home Manager–managed hosts.\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = pkgs.lib.linux-only [ pkgs.acpilight ];\n}\n#+end_src\n\nBind it to keys (emacs-lisp).\n#+begin_src emacs-lisp\n(rasen/exwm-input-set-key (kbd \"\u003cXF86MonBrightnessUp\u003e\")\n                          (lambda () (interactive) (rasen/start-command \"xbacklight -inc 10\")))\n(rasen/exwm-input-set-key (kbd \"\u003cXF86MonBrightnessDown\u003e\")\n                          (lambda () (interactive) (rasen/start-command \"xbacklight -dec 10\")))\n#+end_src\n** Redshift\nRedshift adjusts the color temperature of the screen according to the position of the sun.\n\nBlue light blocks [[https://en.wikipedia.org/wiki/Melatonin][melatonin]] (sleep harmone) secretion, so you feel less sleepy when you stare at computer screen.\nRedshift blocks some blue light (making screen more red), which should improve melatonin secretion and restore sleepiness (which is a good thing).\n\n#+name: nixos-section\n#+begin_src nix\n{\n  services.redshift = {\n    enable = true;\n  };\n  location.provider = \"geoclue2\";\n}\n#+end_src\n** PipeWire\nUse PipeWire as audio server.\n#+name: nixos-section\n#+begin_src nix\n{\n  # PipeWire requires pulseaudio to be disabled.\n  hardware.pulseaudio.enable = false;\n\n  security.rtkit.enable = true;\n  services.pipewire = {\n    enable = true;\n    alsa.enable = true;\n    alsa.support32Bit = true;\n    pulse.enable = true;\n\n\n    media-session.config.bluez-monitor.rules = [\n      {\n        # Matches all cards\n        matches = [ { \"device.name\" = \"~bluez_card.*\"; } ];\n        actions = {\n          \"update-props\" = {\n            \"bluez5.reconnect-profiles\" = [ \"hfp_hf\" \"hsp_hs\" \"a2dp_sink\" ];\n            # mSBC is not expected to work on all headset + adapter combinations.\n            \"bluez5.msbc-support\" = true;\n            # SBC-XQ is not expected to work on all headset + adapter combinations.\n            \"bluez5.sbc-xq-support\" = true;\n          };\n        };\n      }\n      {\n        matches = [\n          # Matches all sources\n          { \"node.name\" = \"~bluez_input.*\"; }\n          # Matches all outputs\n          { \"node.name\" = \"~bluez_output.*\"; }\n        ];\n        actions = {\n          \"node.pause-on-idle\" = false;\n        };\n      }\n    ];\n  };\n}\n#+end_src\n\n=pavucontrol= is PulseAudio Volume Control—a nice utility for controlling pulseaudio settings. (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = pkgs.lib.linux-only [ pkgs.pavucontrol ];\n}\n#+end_src\n\n#+begin_src emacs-lisp\n(defun rasen/pavucontrol ()\n  (interactive)\n  (rasen/switch-start \"Pavucontrol\" \"pavucontrol\"))\n#+end_src\n\nBind volume control commands. (emacs-lisp)\n#+begin_src emacs-lisp\n(rasen/exwm-input-set-key (kbd \"\u003cXF86AudioMute\u003e\")\n                          (lambda () (interactive) (rasen/start-command \"amixer set Master toggle\")))\n(rasen/exwm-input-set-key (kbd \"\u003cXF86AudioRaiseVolume\u003e\")\n                          (lambda () (interactive) (rasen/start-command \"amixer set Master 2%+\")))\n(rasen/exwm-input-set-key (kbd \"\u003cXF86AudioLowerVolume\u003e\")\n                          (lambda () (interactive) (rasen/start-command \"amixer set Master 2%-\")))\n#+end_src\n** Bluetooth\nI have a bluetooth headset, so this enables bluetooth audio in NixOS.\n\n(~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  hardware.bluetooth.enable = true;\n}\n#+end_src\n** ADB\nI need to access my Android device. (~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  services.udev.packages = [ pkgs.android-udev-rules ];\n  programs.adb.enable = true;\n  users.users.rasen.extraGroups = [\"adbusers\"];\n}\n#+end_src\n** fwupd\nfwupd is a service that allows applications to update firmware. (~\u003c\u003cnixos-section\u003e\u003e~)\n#+name: nixos-section\n#+begin_src nix\n{\n  services.fwupd.enable = true;\n}\n#+end_src\n\nExecute the following command to update firmware.\n#+begin_src sh :tangle no\nfwupdmgr get-updates\n#+end_src\n* Browsers\nFirefox is default, Chrome for backup.\n#+name: home-manager-section\n#+begin_src nix\n{\n  home.packages = pkgs.lib.linux-only [\n    pkgs.firefox\n    pkgs.google-chrome\n  ];\n}\n#+end_src\n** Tridactyl\nTridactyl is a Firefox plugin that provides Vim-like bindings.\n\nHere is my config. (~\u003c\u003ctridactylrc\u003e\u003e~)\n#+name: tridactylrc\n#+begin_src fundamental :tangle no\n\" drop all existing configuration\nsanitize tridactyllocal tridactylsync\n\nbind J scrollline -10\nbind K scrollline 10\nbind j scrollline -2\nbind k scrollline 2\n#+end_src\n\nLink tridactyl config to the place the tridactyl can find it. (~\u003c\u003chome-manager-section\u003e\u003e~)\n#+name: home-manager-section\n#+begin_src nix\n{\n  xdg.configFile.\"tridactyl/tridactylrc\".text = ''\n    \u003c\u003ctridactylrc\u003e\u003e\n  '';\n}\n#+end_src\n** Edit text in browser\nI use [[https://github.com/GhostText/GhostText][GhostText]] firefox extension.\n\n~atomic-chrome~ Emacs extension is compatible with it. (emacs-lisp)\n#+begin_src emacs-lisp\n(use-package atomic-chrome\n  :config\n  (setq atomic-chrome-default-major-mode 'markdown-mode)\n  (setq atomic-chrome-buffer-open-style 'frame)\n  (atomic-chrome-start-server))\n#+end_src\n* Evil-mode\n** General\n#+begin_src emacs-lisp\n(use-package evil\n  :disabled t\n  :custom\n  (evil-undo-system 'undo-redo)\n  :config\n  \u003c\u003cevil-config\u003e\u003e\n  (evil-mode 1))\n#+end_src\n\nSwap =.= and =;=.\n#+name: evil-config\n#+begin_src emacs-lisp :tangle no\n(general-def 'normal\n  \";\"    #'evil-repeat\n  \".\"    nil\n  \"C-;\"  #'evil-repeat-pop\n  \"C-.\"  nil)\n\n(general-def 'motion\n  \".\"    #'evil-repeat-find-char\n  \";\"    nil\n  \"g.\"   #'goto-last-change\n  \"g;\"   nil)\n#+end_src\n\n#+name: evil-config\n#+begin_src emacs-lisp :tangle no\n(s-leader-def\n  \";\"  #'eval-expression)\n#+end_src\n\nClose other window.\n#+begin_src emacs-lisp\n(defun rasen/quit-other ()\n  (interactive)\n  (other-window 1)\n  (quit-window))\n\n(s-leader-def\n  \"q\"  #'rasen/quit-other)\n#+end_src\n\nMove to beginning/end of line with =H= and =L= respectively.\n#+name: evil-config\n#+begin_src emacs-lisp :tangle no\n(defun rasen/smart-move-beginning-of-line (arg)\n  \"Move point back to indentation of beginning of line.\n\nMove point to the first non-whitespace character on this line.\nIf point is already there, move to the beginning of the line.\nEffectively toggle between the first non-whitespace character and\nthe beginning of the line.\n\nIf ARG is not nil or 1, move forward ARG - 1 lines first.  If\npoint reaches the beginning or end of the buffer, stop there.\"\n  (interactive \"^p\")\n  (setq arg (or arg 1))\n\n  ;; Move lines first\n  (when (/= arg 1)\n    (let ((line-move-visual nil))\n      (forward-line (1- arg))))\n\n  (let ((orig-point (point)))\n    (back-to-indentation)\n    (when (= orig-point (point))\n      (move-beginning-of-line 1))))\n\n(general-def 'motion\n  \"H\"  #'rasen/smart-move-beginning-of-line\n  \"L\"  #'evil-end-of-line)\n#+end_src\n\nSave buffer with =SPC SPC=.\n#+begin_src emacs-lisp\n(defun rasen/save-buffer (arg)\n  \"Save current buffer.  With PREFIX, save all buffers.\"\n  (interactive \"P\")\n  (if arg\n      (save-some-buffers)\n    (save-buffer)))\n\n(with-eval-after-load \"evil\"\n  (s-leader-def 'normal\n    \"SPC\" #'rasen/save-buffer)\n  (s-leader-def\n    \"s-SPC\" #'save-some-buffers))\n#+end_src\n** Swap k and j\nWith workman layout, =j= is located on qwerty =y= and =k=---on qwerty =n=; thus =j= is higher than =k=, and it is not convenient to press lower key for going up. Just swap them.\n#+name: evil-config\n#+begin_src emacs-lisp :tangle no\n(general-def 'motion\n  \"k\"    #'evil-next-visual-line\n  \"j\"    #'evil-previous-visual-line\n  \"gk\"   #'evil-next-line\n  \"gj\"   #'evil-previous-line)\n\n(general-def 'operator\n  \"k\"    #'evil-next-line\n  \"j\"    #'evil-previous-line\n  \"gk\"   #'evil-next-visual-line\n  \"gj\"   #'evil-previous-visual-line)\n\n(general-def 'motion\n  \"C-h\"  #'windmove-left\n  \"C-k\"  #'windmove-down\n  \"C-j\"  #'windmove-up\n  \"C-l\"  #'windmove-right)\n\n(general-swap-key nil 'motion\n \"C-w j\" \"C-w k\")\n#+end_src\n** evil-numbers\nI use Vim's =C-a= and =C-x= (increment/decrement number at point) a lot.\n=evil-numbers= provides that functionality for evil.\n#+begin_src emacs-lisp\n(use-package evil-numbers\n  :after evil\n  :general\n  ('normal\n   \"C-a\" #'evil-numbers/inc-at-pt\n   \"C-x\" #'evil-numbers/dec-at-pt))\n#+end_src\n\nNow, remap =C-x= to =RET=. (Because =C-x= is used for decrementing numbers.)\n#+name: evil-config\n#+begin_src emacs-lisp\n(general-def 'motion\n  \"RET\" (lookup-key (current-global-map) (kbd \"C-x\")))\n;; Unmap it from magit\n(general-def magit-file-mode-map\n  \"C-x\" nil)\n#+end_src\n** evil-collection\nevil-collection is a collection of evil bindings for different modes.\n\nThese variables need to be set before evil loads (which seems to be required by =general=) or shortly after. So the following needs to be placed before general:\n#+name: before-general\n#+begin_src emacs-lisp\n(setq evil-want-integration t\n      evil-want-keybinding nil)\n#+end_src\n\n#+begin_src emacs-lisp\n(require 'warnings)\n(add-to-list 'warning-suppress-types '(evil-collection))\n\n(use-package evil-collection\n  :disabled t\n  :after (evil)\n  :init\n  (setq evil-want-integration t\n        evil-want-keybinding nil)\n  :config\n  (defun rasen/rotate-keys (_mode mode-keymaps \u0026rest _rest)\n    ;; (evil-collection-translate-key 'normal mode-keymaps\n    ;;   \"k\" \"j\"\n    ;;   \"j\" \"k\"\n    ;;   \"gk\" \"gj\"\n    ;;   \"gj\" \"gk\"\n    ;;   (kbd \"C-j\") (kbd \"C-k\")\n    ;;   (kbd \"C-k\") (kbd \"C-j\")\n    ;;   (kbd \"M-j\") (kbd \"M-k\")\n    ;;   (kbd \"M-k\") (kbd \"M-j\")\n    ;;   \".\" \";\"\n    ;;   \";\" \".\")\n    )\n  (add-hook 'evil-collection-setup-hook #'rasen/rotate-keys)\n\n  (setq evil-collection-m","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frasendubi%2Fdotfiles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frasendubi%2Fdotfiles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frasendubi%2Fdotfiles/lists"}