<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>peterstuart.org</title>
        <link>http://www.peterstuart.org</link>
        <description><![CDATA[Articles from peterstuart.org]]></description>
        <atom:link href="http://www.peterstuart.org/rss" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Sun, 02 Jul 2023 00:00:00 UT</lastBuildDate>
        <item>
    <title>Quickly Opening .env/.envrc Files in Emacs</title>
    <link>https://www.peterstuart.org/posts/2023-07-02-emacs-project-dotenv-files/index.html</link>
    <description><![CDATA[<article>
  <h1>Quickly Opening .env/.envrc Files in Emacs</h1>
  <p class="subtitle">
    by Peter Stuart on July  2, 2023
  </p>
  <section>
    <p>I use <a href="https://www.gnu.org/software/emacs/">Emacs</a>’s built-in <a href="https://github.com/emacs-mirror/emacs/blob/master/lisp/progmodes/project.el">project.el</a> to manage projects, and <code>.env</code> or <code>.envrc</code> files to manage environment variables for those projects. Those files are usually not tracked by <code>git</code>, so <code>project-file-file</code> doesn’t offer them as options<span><label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote">Invoking <code>project-find-file</code> with a prefix argument <strong>will</strong> find uncommitted files, but also all other files that have been added to <code>.gitignore</code>, which I don’t want.<br />
<br />
</span></span>, so I wrote a small function that will find all <code>.env</code> and <code>.envrc</code> files (including files with suffixes, like <code>.env.local</code> and <code>.envrc.sample</code>) in the project root:</p>
<div class="sourceCode" id="cb1" data-org-language="emacs-lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a>(<span class="kw">defun</span><span class="fu"> ps/project-find-dotenv-file </span>()</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a>  (interactive)</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a>  (<span class="kw">let*</span> ((project           (project-current <span class="kw">t</span>))</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a>         (default-directory (project-root project))</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a>         (files             (file-expand-wildcards <span class="st">&quot;</span><span class="sc">\\</span><span class="st">`.env</span><span class="sc">\\</span><span class="st">(rc</span><span class="sc">\\</span><span class="st">)?</span><span class="sc">\\</span><span class="st">(</span><span class="sc">\\</span><span class="st">..+</span><span class="sc">\\</span><span class="st">)?</span><span class="sc">\\</span><span class="st">&#39;&quot;</span> <span class="kw">nil</span> <span class="kw">t</span>))</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a>         (completion        (<span class="kw">lambda</span> (str pred flag)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a>                              (pcase flag</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a>                                (&#39;metadata</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a>                                 `(metadata (category . file)))</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a>                                (_</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a>                                 (all-completions str files pred)))))</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true"></a>         (file              (completing-read <span class="st">&quot;Find .env/.envrc file: &quot;</span> completion)))</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true"></a>    (find-file file)))</span></code></pre></div>
<p><code>completion</code> could be a simple list of strings instead of a <code>lambda</code>, but I use <a href="https://github.com/minad/marginalia">marginalia</a> to annotate completions with useful information, which requires that the category metadata be set on completions so that it knows that (in this case) it should treat the completions as files when annotating them.</p>
<p><img src="../../images/2023-07-02-emacs-projects-dotenv-files/example.png" /></p>
<p>I bind it in <code>project-prefix-map</code> and also add it to the project switcher:</p>
<div class="sourceCode" id="cb2" data-org-language="emacs-lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a>(<span class="kw">use-package</span> project</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>  :ensure <span class="kw">nil</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a>  :init</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a>  (<span class="kw">setq</span> project-switch-commands &#39;((magit-project-status        <span class="st">&quot;Magit&quot;</span>)</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a>                                  (project-find-file           <span class="st">&quot;Find file&quot;</span>)</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a>                                  (ps/project-find-dotenv-file <span class="st">&quot;Find .env file&quot;</span>)))</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a>  :bind</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a>  (:map project-prefix-map</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true"></a>        (<span class="st">&quot;m&quot;</span> . magit-project-status)</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true"></a>        (<span class="st">&quot;v&quot;</span> . ps/project-find-dotenv-file)))</span></code></pre></div>
  </section>
</article>
]]></description>
    <pubDate>Sun, 02 Jul 2023 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2023-07-02-emacs-project-dotenv-files/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>
<item>
    <title>Fixing 1Password on NixOS/Wayland with Nvidia Graphics</title>
    <link>https://www.peterstuart.org/posts/2023-07-01-1password-nixos-wayland-nvidia/index.html</link>
    <description><![CDATA[<article>
  <h1>Fixing 1Password on NixOS/Wayland with Nvidia Graphics</h1>
  <p class="subtitle">
    by Peter Stuart on July  1, 2023
  </p>
  <section>
    <p>I’m running Wayland on NixOS using Nvidia graphics. <a href="https://1password.com/">1Password</a><span><label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote">Available in the <code>_1password-gui</code> package.<br />
<br />
</span></span> is not compatible by default with that setup and starts up with a black window:</p>
<p><img src="../../images/2023-07-01-1password-nixos-wayland-nvidia/black-window.png" /></p>
<p>1Password support <a href="https://1password.community/discussion/comment/624768">suggests</a> running 1Password with the <code>--disable-gpu-sandbox</code> option, which works from the command line, but to use that option when launching 1Password with the <a href="https://www.gnome.org/">GNOME</a> launcher, we need to edit the <code>.desktop</code> file that’s included with the package. Since that file is immutable in NixOS, I wrote an <a href="https://nixos.wiki/wiki/Overlays">overlay</a><span><label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle"/><span class="sidenote">Based on <a href="https://www.reddit.com/r/NixOS/comments/scf0ui/comment/hu6xfn8/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button">a comment</a> in the NixOS subreddit.<br />
<br />
</span></span> for the <code>_1password-gui</code> package which overrides that file.</p>
<p>In my <a href="https://github.com/nix-community/home-manager">home-manager</a><span><label for="sn-2" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-2" class="margin-toggle"/><span class="sidenote">You should be able to do something similar if you are using NixOS to install 1Password for the entire system, instead of using home-manager.<br />
<br />
</span></span> config:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="ex">nixpkgs.overlays</span> = [ (import ./overlays/1password.nix) ];</span></code></pre></div>
<p>… and in <code>overlays/1password.nix</code>:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode nix"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="ex">final</span>: prev: {</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>  <span class="ex">_1password-gui</span> = prev._1password-gui.overrideAttrs (oldAttrs: {</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a>    <span class="ex">postInstall</span> = (oldAttrs.postInstall or <span class="st">&quot;&quot;</span>) <span class="ex">+</span> <span class="st">&#39;&#39;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a>      <span class="ex">substituteInPlace</span> <span class="va">$out</span>/share/applications/1password.desktop <span class="kw">\</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a>        <span class="ex">--replace</span> <span class="st">&quot;Exec=1password&quot;</span> <span class="st">&quot;Exec=1password --disable-gpu-sandbox&quot;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a>    <span class="st">&#39;&#39;</span>;</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a>  });</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a>}</span></code></pre></div>
<p>We can see the result of the override by inspecting <code>~/.nix-profile/share/applications/1password.desktop</code>, which is symlinked to the <code>.desktop</code> file in the Nix store.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode ini"><code class="sourceCode ini"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="kw">[Desktop Entry]</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="dt">Name</span><span class="ot">=</span><span class="st">1Password</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a><span class="dt">Exec</span><span class="ot">=</span><span class="st">1password --disable-gpu-sandbox %U</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a><span class="dt">Terminal</span><span class="ot">=</span><span class="kw">false</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a><span class="dt">Type</span><span class="ot">=</span><span class="st">Application</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a><span class="dt">Icon</span><span class="ot">=</span><span class="st">1password</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a><span class="dt">StartupWMClass</span><span class="ot">=</span><span class="st">1Password</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a><span class="dt">Comment</span><span class="ot">=</span><span class="st">Password manager and secure wallet</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a><span class="dt">MimeType</span><span class="ot">=</span><span class="st">x-scheme-handler/onepassword;</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a><span class="dt">Categories</span><span class="ot">=</span><span class="st">Office;</span></span></code></pre></div>
<p>The file looks correct and 1Password works after being launched from the GNOME launcher:<span><label for="sn-3" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-3" class="margin-toggle"/><span class="sidenote">1Password doesn’t quit when the window is closed, so if you already have an instance running without the necessary option, you may need to <code>killall 1password</code> to shut it down before launching it again.<br />
<br />
</span></span></p>
<p><img src="../../images/2023-07-01-1password-nixos-wayland-nvidia/working.png" /></p>
  </section>
</article>
]]></description>
    <pubDate>Sat, 01 Jul 2023 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2023-07-01-1password-nixos-wayland-nvidia/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>
<item>
    <title>Open Shortcut Stories Using Embark</title>
    <link>https://www.peterstuart.org/posts/2023-06-24-embark-shortcut-targets/index.html</link>
    <description><![CDATA[<article>
  <h1>Open Shortcut Stories Using Embark</h1>
  <p class="subtitle">
    by Peter Stuart on June 24, 2023
  </p>
  <section>
    <p>I’m working on a project using <a href="https://www.shortcut.com/">Shortcut</a>, where every commit message must include the ID of a Shortcut story, in this format:</p>
<pre class="text"><code>[sc-12345] Commit message
</code></pre>
<p>I wanted to be able to quickly open a Shortcut story in a browser from the ID. Since I use <a href="https://github.com/oantolin/embark">Embark</a> in <a href="https://www.gnu.org/software/emacs/">Emacs</a>, I created a new “target finder” which converts text like <code>[sc-12345]</code> into a URL, and added it to <code>embark-target-finders</code>. The target finder is based on the “short Wikipedia links” example in the Embark manual. You can find it at <code>info:(embark) New target example in regular buffers - short Wikipedia links</code>.</p>
<div class="sourceCode" id="cb2" data-org-language="emacs-lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a>(<span class="kw">defvar</span><span class="fu"> ps/embark-target-shortcut-story-workspace </span><span class="kw">nil</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>  <span class="st">&quot;The Shortcut workspace used when creating story URLs.&quot;</span>)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a>(defconst ps/embark-target-shortcut-story--start-end-regexp</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a>  <span class="st">&quot;SCsc</span><span class="sc">\\</span><span class="st">-[0-9]+&quot;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a>  <span class="st">&quot;The regexp used to find the beginning and end of Shortcut story IDs.&quot;</span>)</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a>(<span class="kw">defun</span><span class="fu"> ps/embark-target-shortcut-story </span>()</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true"></a>  <span class="st">&quot;Target a Shortcut story ID at point of the form sc-[story-id].</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true"></a></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true"></a><span class="st">`ps/embark-target-shortcut-story-workspace&#39; must be set or this</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true"></a><span class="st">function will return nil.&quot;</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true"></a>  (<span class="kw">when</span> ps/embark-target-shortcut-story-workspace</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true"></a>    (save-excursion</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true"></a>      (<span class="kw">let*</span> ((start (<span class="kw">progn</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true"></a>                      (skip-chars-backward ps/embark-target-shortcut-story--start-end-regexp)</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true"></a>                      (point)))</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true"></a>             (end   (<span class="kw">progn</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true"></a>                      (skip-chars-forward ps/embark-target-shortcut-story--start-end-regexp)</span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true"></a>                      (point)))</span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true"></a>             (str   (buffer-substring-no-properties start end)))</span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true"></a>        (save-match-data</span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true"></a>          (<span class="kw">when</span> (string-match <span class="st">&quot;sc-</span><span class="sc">\\</span><span class="st">([0-9]+</span><span class="sc">\\</span><span class="st">)&quot;</span> str)</span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true"></a>            `(url</span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true"></a>              ,(<span class="kw">format</span> <span class="st">&quot;https://app.shortcut.com/%s/story/%s&quot;</span></span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true"></a>                       ps/embark-target-shortcut-story-workspace</span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true"></a>                       (match-string <span class="dv">1</span> str))</span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true"></a>              ,start . ,end)))))))</span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true"></a></span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true"></a>(add-to-list &#39;embark-target-finders &#39;ps/embark-target-shortcut-story)</span></code></pre></div>
<p>When looking at Git logs, or in any other buffer, I can invoke <code>embark-dwim</code> when the point is on a Shortcut story ID, and it will open in my browser.</p>
<p>Since Shortcut story URLs include a workspace name, you must set <code>ps/embark-target-shortcut-story-workspace</code> for the target finder to generate a URL. For example, if <code>ps/embark-target-shortcut-story-workspace</code> is set to <code>"my-workspace"</code>, and the point is on the story ID <code>[sc-12345]</code>, <code>embark-dwim</code> will open <code>https://app.shortcut.com/my-workspace/story/12345</code> in your browser.</p>
  </section>
</article>
]]></description>
    <pubDate>Sat, 24 Jun 2023 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2023-06-24-embark-shortcut-targets/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>
<item>
    <title>Notify When Compilation Buffers Finish</title>
    <link>https://www.peterstuart.org/posts/2023-06-04-compilation-notifications/index.html</link>
    <description><![CDATA[<article>
  <h1>Notify When Compilation Buffers Finish</h1>
  <p class="subtitle">
    by Peter Stuart on June  4, 2023
  </p>
  <section>
    <p>I often compile projects in <a href="https://www.gnu.org/software/emacs">Emacs</a> using <code>project-compile</code>, and then don’t notice that they finished because the compilation window is no longer visible, so I added the following code to my config. It adds a hook to <code>compilation-mode</code> which alerts me when compilation finishes, only if the compilation buffer is not visible in a focused frame. It requires the <a href="https://github.com/jwiegley/alert">alert</a> package.</p>
<div class="sourceCode" id="cb1" data-org-language="emacs-lisp"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a>(<span class="kw">defun</span><span class="fu"> ps/buffer-visible-in-focused-frame-p </span>(buffer)</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a>  <span class="st">&quot;Return t if BUFFER is visible in the focused frame, or nil otherwise.&quot;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a>  (<span class="kw">let</span> ((windows (get-buffer-window-list buffer <span class="kw">nil</span> <span class="kw">t</span>)))</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a>    (cl-some (<span class="kw">lambda</span> (window)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a>               (<span class="kw">and</span> (window-live-p window)</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a>                    (frame-focus-state (window-frame window))))</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a>             windows)))</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a>(<span class="kw">defun</span><span class="fu"> ps/compilation-finish-alert </span>(buffer result)</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a>  <span class="st">&quot;Alert the user when a compilation buffer finishes.</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true"></a><span class="st">If the compilation buffer is visible in the focused frame, the</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true"></a><span class="st">user will not be alerted.&quot;</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true"></a>  (<span class="kw">unless</span> (ps/buffer-visible-in-focused-frame-p buffer)</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true"></a>    (alert (<span class="kw">format</span> <span class="st">&quot;Compilation %s&quot;</span> result)</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true"></a>           :id (<span class="kw">format</span> <span class="st">&quot;ps/compilation-finish-alert-&quot;</span> (buffer-name buffer))</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true"></a>           :never-persist <span class="kw">t</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true"></a>           :buffer buffer)))</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true"></a></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true"></a>(add-hook &#39;compilation-finish-functions &#39;ps/compilation-finish-alert)</span></code></pre></div>
  </section>
</article>
]]></description>
    <pubDate>Sun, 04 Jun 2023 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2023-06-04-compilation-notifications/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>
<item>
    <title>Making XMonad Work with Zoom</title>
    <link>https://www.peterstuart.org/posts/2021-09-06-xmonad-zoom/index.html</link>
    <description><![CDATA[<article>
  <h1>Making XMonad Work with Zoom</h1>
  <p class="subtitle">
    by Peter Stuart on September  6, 2021
  </p>
  <section>
    <p>Making <a href="https://xmonad.org/">XMonad</a> work smoothly with <a href="https://zoom.us/">Zoom</a> takes some configuration. Here are some problems I’ve run into, with the solutions I’ve found.</p>
<h2 id="screen-sharing">Screen Sharing</h2>
<p>If Zoom doesn’t give you the option to share individual windows when screen sharing, you need to use <code>ewmh</code> from <a href="https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-EwmhDesktops.html"><code>XMonad.Hooks.EwmhDesktops</code></a>.<span><label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote"><a href="https://www.reddit.com/r/xmonad/comments/lztqbd/xmonad_window_sharing_with_zoom/">Reddit: xmonad window sharing with Zoom</a><br />
<br />
</span></span></p>
<p>If the “You are screen sharing” / “Stop Share” controls which appear at the top of the screen when you are screen sharing have a black background, and sometimes disappear when you change windows, you need to use a compositor. I use <a href="https://github.com/yshui/picom">picom</a>.</p>
<h2 id="floating-notification-windows">Floating Notification Windows</h2>
<p>Zoom shows notification windows when you join audio (eg. “You are connected to computer audio”) and when people start screen sharing. By default, these windows will be tiled, but they should be floated. Because many of the windows change their title shortly after they are created, matching by title in a custom <code>manageHook</code> doesn’t work, since that doesn’t watch for changes to window titles. Instead, you need to also observe window title changes using <a href="https://hackage.haskell.org/package/xmonad-contrib-0.16/docs/XMonad-Hooks-DynamicProperty.html"><code>XMonad.Hooks.DynamicProperty</code></a>.<span><label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle"/><span class="sidenote"><a href="https://bbs.archlinux.org/viewtopic.php?pid=1680066#p1680066">Ethan Schoonover on the Arch Linux forums</a><br />
<br />
</span></span></p>
<p>I have a single <code>manageZoomHook</code> which I include in both my custom <code>manageHook</code> and my custom <code>handleEventHook</code> (using <code>dynamicTitle</code> from <code>XMonad.Hooks.DynamicProperty</code>):</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a>manageZoomHook <span class="ot">=</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a>  composeAll <span class="op">$</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a>    [ (className <span class="op">=?</span> zoomClassName) <span class="op">&lt;&amp;&amp;&gt;</span> shouldFloat <span class="op">&lt;$&gt;</span> title <span class="op">--&gt;</span> doFloat,</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a>      (className <span class="op">=?</span> zoomClassName) <span class="op">&lt;&amp;&amp;&gt;</span> shouldSink <span class="op">&lt;$&gt;</span> title <span class="op">--&gt;</span> doSink</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a>    ]</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a>  <span class="kw">where</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a>    zoomClassName <span class="ot">=</span> <span class="st">&quot;zoom&quot;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a>    tileTitles <span class="ot">=</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a>      [ <span class="st">&quot;Zoom - Free Account&quot;</span>, <span class="co">-- main window</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a>        <span class="st">&quot;Zoom - Licensed Account&quot;</span>, <span class="co">-- main window</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a>        <span class="st">&quot;Zoom&quot;</span>, <span class="co">-- meeting window on creation</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true"></a>        <span class="st">&quot;Zoom Meeting&quot;</span> <span class="co">-- meeting window shortly after creation</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true"></a>      ]</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true"></a>    shouldFloat title <span class="ot">=</span> title <span class="ot">`notElem`</span> tileTitles</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true"></a>    shouldSink title <span class="ot">=</span> title <span class="ot">`elem`</span> tileTitles</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true"></a>    doSink <span class="ot">=</span> (ask <span class="op">&gt;&gt;=</span> doF <span class="op">.</span> W.sink) <span class="op">&lt;+&gt;</span> doF W.swapDown</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true"></a></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true"></a>myManageHook <span class="ot">=</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true"></a>  manageZoomHook</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true"></a>    <span class="op">&lt;+&gt;</span> manageDocks</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true"></a>    <span class="op">&lt;+&gt;</span> manageHook defaultConfig</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true"></a></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true"></a>myHandleEventHook <span class="ot">=</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true"></a>  <span class="fu">mconcat</span></span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true"></a>    [ dynamicTitle manageZoomHook,</span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true"></a>      docksEventHook,</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true"></a>      handleEventHook defaultConfig</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true"></a>    ]</span></code></pre></div>
  </section>
</article>
]]></description>
    <pubDate>Mon, 06 Sep 2021 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2021-09-06-xmonad-zoom/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>
<item>
    <title>Replace Your Terminal with Emacs</title>
    <link>https://www.peterstuart.org/posts/2021-07-24-emacs-terminal/index.html</link>
    <description><![CDATA[<article>
  <h1>Replace Your Terminal with Emacs</h1>
  <p class="subtitle">
    by Peter Stuart on July 24, 2021
  </p>
  <section>
    <p>When I’m working in <a href="https://www.gnu.org/software/emacs">Emacs</a>, I use <a href="https://github.com/akermu/emacs-libvterm"><code>vterm</code></a> as a terminal emulator. Rather than use a separate terminal emulator when I’m not already in Emacs, I’d like to be able to easily open a new Emacs frame with a fresh terminal in it. To do that, I wrote a script which runs <code>emacsclient</code><span><label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote">This requires that you are running <a href="https://www.emacswiki.org/emacs/EmacsAsDaemon">Emacs as a daemon</a>.<br />
<br />
</span></span>, opens a new frame, and launches <code>vterm</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="ex">emacsclient</span> -cn --eval <span class="st">&quot;(progn (x-focus-frame nil) (vterm t))&quot;</span></span></code></pre></div>
<ul>
<li><code>-cn</code> creates a new frame, but does not wait for it to close before finishing the script.</li>
<li><code>(x-focus-frame nil)</code> focuses the new frame. On macOS, the new frame will often appear in the background without this.</li>
<li><code>(vterm t)</code> creates a new <code>vterm</code> buffer. Without the <code>t</code> argument, it would switch to an already-existing <code>vterm</code> buffer, if there was one.</li>
</ul>
<p>On Linux, with <a href="https://xmonad.org/">XMonad</a> as my window manager, I bind this script to <kbd>S-s-&lt;return&gt;</kbd>. On macOS, I use <a href="https://www.alfredapp.com/">Alfred</a> to bind it to <kbd class="non-code">⇧⌘⏎</kbd>.<span><label for="sn-1" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-1" class="margin-toggle"/><span class="sidenote">Alfred runs the script from the path of the workflow that contains it, so I add <code>cd ~</code> to the beginning of the script on macOS.<br />
<br />
</span></span></p>
  </section>
</article>
]]></description>
    <pubDate>Sat, 24 Jul 2021 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2021-07-24-emacs-terminal/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>
<item>
    <title>Writing a Type-Level Switch Statement in TypeScript</title>
    <link>https://www.peterstuart.org/posts/2020-03-07-writing-type-level-switch-statement-typescript/index.html</link>
    <description><![CDATA[<article>
  <h1>Writing a Type-Level Switch Statement in TypeScript</h1>
  <p class="subtitle">
    by Peter Stuart on March  7, 2020
  </p>
  <section>
    <p>I recently came across a situation where I needed to write a very long conditional type<span><label for="sn-0" class="margin-toggle sidenote-number"></label><input type="checkbox" id="sn-0" class="margin-toggle"/><span class="sidenote">Conditional types have the format <code>T extends U ? X : Y</code>. Read more about them in <a href="https://www.typescriptlang.org/docs/handbook/2/conditional-types.html">the TypeScript docs</a>.<br />
<br />
</span></span> that looked something like this:</p>
<p>Rather than nesting <code>extends</code> expressions, which only supports a fixed number of conditions and results in a <a href="https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)">pyramid of doom</a> in the definition of <code>Foo</code>, this could be better expressed using a <code>Switch</code> type:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="kw">type</span> Switch<span class="op">&lt;</span>Value<span class="op">,</span> [</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a>  [Match1<span class="op">,</span> Result1]<span class="op">,</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a>  [Match2<span class="op">,</span> Result2]<span class="op">,</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a>  [Match3<span class="op">,</span> Result3]<span class="op">,</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a>  <span class="op">...</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a>]<span class="op">&gt;</span> <span class="op">=</span> <span class="op">...</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a><span class="kw">type</span> Test1 <span class="op">=</span> Switch<span class="op">&lt;</span><span class="dt">string</span><span class="op">,</span> [</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a>  [number<span class="op">,</span> <span class="st">&quot;number&quot;</span>]<span class="op">,</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a>  [boolean<span class="op">,</span> <span class="st">&quot;boolean&quot;</span>]<span class="op">,</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a>  [string<span class="op">,</span> <span class="st">&quot;string&quot;</span>]</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true"></a>]<span class="op">&gt;;</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true"></a><span class="co">// Test1 = &quot;string&quot;</span></span></code></pre></div>
<p><code>Switch</code> supports an arbitrary number of conditions, and we can write a recursive definition for it.</p>
<h2 id="defining-switch">Defining Switch</h2>
<p>To iterate through the array of conditions, I use the <code>List.Head</code> and <code>List.Tail</code> types from the excellent <a href="https://github.com/pirix-gh/ts-toolbelt">ts-toolbelt</a> library:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="im">import</span> {List} from <span class="st">&#39;ts-toolbelt&#39;</span><span class="op">;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a><span class="kw">type</span> Test1 <span class="op">=</span> List<span class="op">.</span><span class="at">Head</span><span class="op">&lt;</span>[<span class="dt">boolean</span><span class="op">,</span> <span class="dt">string</span><span class="op">,</span> <span class="dt">number</span>]<span class="op">&gt;;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a><span class="co">// Test1 = boolean</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a><span class="kw">type</span> Test2 <span class="op">=</span> List<span class="op">.</span><span class="at">Tail</span><span class="op">&lt;</span>[<span class="dt">boolean</span><span class="op">,</span> <span class="dt">string</span><span class="op">,</span> <span class="dt">number</span>]<span class="op">&gt;;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a><span class="co">// Test2 = [string, number]</span></span></code></pre></div>
<p>Using <code>Head</code> and <code>Tail</code>, we can define <code>Switch</code> like this:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="kw">type</span> Switch<span class="op">&lt;</span>T<span class="op">,</span> Conditions extends Array<span class="op">&lt;</span>[<span class="dt">any</span><span class="op">,</span> <span class="dt">any</span>]<span class="op">&gt;&gt;</span> <span class="op">=</span> </span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a>  List<span class="op">.</span><span class="at">Head</span><span class="op">&lt;</span>Conditions<span class="op">&gt;</span> extends <span class="dt">never</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a>    <span class="op">?</span> <span class="dt">never</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a>    <span class="op">:</span> T extends List<span class="op">.</span><span class="at">Head</span><span class="op">&lt;</span>Conditions<span class="op">&gt;</span>[<span class="dv">0</span>]</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a>      <span class="op">?</span> List<span class="op">.</span><span class="at">Head</span><span class="op">&lt;</span>Conditions<span class="op">&gt;</span>[<span class="dv">1</span>]</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a>      <span class="op">:</span> Switch<span class="op">&lt;</span>T<span class="op">,</span> List<span class="op">.</span><span class="at">Tail</span><span class="op">&lt;</span>Conditions<span class="op">&gt;&gt;;</span></span></code></pre></div>
<p>We can confirm that it works with a few test types:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="kw">type</span> Test1 <span class="op">=</span> Switch<span class="op">&lt;</span><span class="dt">string</span><span class="op">,</span> [</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a>  [number<span class="op">,</span> <span class="st">&quot;number&quot;</span>]<span class="op">,</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a>  [boolean<span class="op">,</span> <span class="st">&quot;boolean&quot;</span>]<span class="op">,</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a>  [string<span class="op">,</span> <span class="st">&quot;string&quot;</span>]</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a>]<span class="op">&gt;;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a><span class="co">// Test1 = &quot;string&quot;</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a><span class="kw">type</span> Test2 <span class="op">=</span> Switch<span class="op">&lt;</span><span class="dt">object</span><span class="op">,</span> [</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a>  [number<span class="op">,</span> <span class="st">&quot;number&quot;</span>]<span class="op">,</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a>  [boolean<span class="op">,</span> <span class="st">&quot;boolean&quot;</span>]<span class="op">,</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a>  [string<span class="op">,</span> <span class="st">&quot;string&quot;</span>]</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true"></a>]<span class="op">&gt;;</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true"></a><span class="co">// Test2 = never</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true"></a></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true"></a><span class="kw">type</span> Test3 <span class="op">=</span> Switch<span class="op">&lt;</span><span class="dt">string</span><span class="op">,</span> []<span class="op">&gt;;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true"></a><span class="co">// Test3 = never</span></span></code></pre></div>
<p>To add a default case, match against <code>any</code> in the last condition:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="kw">type</span> Test1 <span class="op">=</span> Switch<span class="op">&lt;</span><span class="dt">string</span><span class="op">,</span> [</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a>  [number<span class="op">,</span> <span class="st">&quot;number&quot;</span>]<span class="op">,</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a>  [any<span class="op">,</span> <span class="st">&quot;default case&quot;</span>]</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a>]<span class="op">&gt;;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a><span class="co">// Test1 = &quot;default case&quot;</span></span></code></pre></div>
<p>For explanations of conditional types, implementing recursive types, and other advanced type techniques, check out these articles:</p>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html">Advanced Types</a> in the TypeScript docs</li>
<li><a href="https://www.freecodecamp.org/news/typescript-curry-ramda-types-f747e99744ab/">How to master advanced TypeScript patterns</a> by Pierre-Antoine Mills (the author of <a href="https://github.com/pirix-gh/ts-toolbelt">ts-toolbelt</a>)</li>
</ul>
  </section>
</article>
]]></description>
    <pubDate>Sat, 07 Mar 2020 00:00:00 UT</pubDate>
    <guid>https://www.peterstuart.org/posts/2020-03-07-writing-type-level-switch-statement-typescript/index.html</guid>
    <dc:creator>Peter Stuart</dc:creator>
</item>

    </channel>
</rss>
