mirror of
https://github.com/SqrtMinusOne/sqrtminusone.github.io.git
synced 2025-12-11 00:03:02 +03:00
2559 lines
574 KiB
XML
2559 lines
574 KiB
XML
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||
<channel>
|
||
<title>Index on SqrtMinusOne</title>
|
||
<link>https://sqrtminusone.xyz/</link>
|
||
<description>Recent content in Index on SqrtMinusOne</description>
|
||
<generator>Hugo -- gohugo.io</generator>
|
||
<language>en-us</language>
|
||
<lastBuildDate>Tue, 10 May 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/index.xml" rel="self" type="application/rss+xml" />
|
||
<item>
|
||
<title>Extending elfeed with PDF viewer and subtitles fetcher</title>
|
||
<link>https://sqrtminusone.xyz/posts/2022-05-09-pdf/</link>
|
||
<pubDate>Tue, 10 May 2022 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2022-05-09-pdf/</guid>
|
||
<content type="html">
|
||
<h2 id="intro">Intro</h2>
|
||
<p><a href="https://github.com/skeeto/elfeed">elfeed</a> is one of the most popular Emacs packages, and it&rsquo;s also one in which I ended up investing a lot of effort. I wrote about the <a href="https://sqrtminusone.xyz/posts/2021-09-07-emms/">EMMS integration</a> and even made a <a href="https://github.com/SqrtMinusOne/elfeed-summary">custom frontpage</a> to my liking.</p>
|
||
<p>However, sites frequently limit the amount of information shipped in the RSS feed. Oftentimes the entry doesn&rsquo;t include the entire content (of which, by the way, this blog was guilty).</p>
|
||
<p>Also, there&rsquo;s non-textual content, of which in this post I consider YouTube subscriptions. It&rsquo;s possible to watch YouTube from elfeed, for instance with the aforementioned EMMS integration, but we can do more.</p>
|
||
<p>So, the plan for the post is to discuss:</p>
|
||
<ul>
|
||
<li>using <a href="https://github.com/eafer/rdrview">rdrview</a> to extend elfeed articles;</li>
|
||
<li>using <a href="https://pandoc.org">pandoc</a> and LaTeX to convert articles to PDFs;</li>
|
||
<li>using <a href="https://github.com/jdepoix/youtube-transcript-api">youtube-transcript-api</a> to download YouTube subtitles and <a href="https://github.com/sachac/subed">subed</a> to control the MPV playback;</li>
|
||
</ul>
|
||
<p>Also, heads up! You&rsquo;ll need lexical binding enabled for the code blocks. The easiest way to accomplish this is to add the following to the first line of <code>init.el</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span><span style="color:#408080;font-style:italic">;;; -*- lexical-binding: t -*-</span>
|
||
</span></span></code></pre></div><h2 id="rdrview">rdrview</h2>
|
||
<p><a href="https://github.com/eafer/rdrview">rdrview</a> is a command-line tool to strip webpages from clutter, extracting only parts related to the actual content. It&rsquo;s a standalone port of the corresponding feature of Firefox, called <a href="https://support.mozilla.org/en-US/kb/firefox-reader-view-clutter-free-web-pages">Reader View</a>.</p>
|
||
<p>It seems like the tool <a href="https://repology.org/project/rdrview/versions">isn&rsquo;t available</a> in a whole lot of package repositories, but it&rsquo;s pretty easy to compile. I&rsquo;ve put together a <a href="https://github.com/SqrtMinusOne/channel-q/blob/master/rdrview.scm">Guix definition</a>, which <em>one day</em> I&rsquo;ll submit to upstream.</p>
|
||
<h3 id="integrating-rdrview-with-emacs">Integrating rdrview with Emacs</h3>
|
||
<p>Let&rsquo;s start by integrating <code>rdrview</code> with Emacs. In the general case, we want to fetch both metadata and the actual content from the page.</p>
|
||
<p>However, the interface of <code>rdrview</code> is a bit awkward in this part, so we have the following options:</p>
|
||
<ul>
|
||
<li>call <code>rdrview</code> two times: with <code>-M</code> flag to fetch the metadata, and without the flag to fetch the HTML;</li>
|
||
<li>call <code>rdrview</code> with <code>-T</code> flag to append the metadata to the resulting HTML.</li>
|
||
</ul>
|
||
<p>I&rsquo;ve decided to go with the second option. Here is a function that calls rdrview with the required flags:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/rdrview-get</span> (<span style="color:#19177c">url</span> <span style="color:#19177c">callback</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Get the rdrview representation of URL.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">Call CALLBACK with the output.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">buffer</span> (<span style="color:#19177c">generate-new-buffer</span> <span style="color:#ba2121">&#34;rdrview&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">proc</span> (<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;rdrview&#34;</span> <span style="color:#19177c">buffer</span> <span style="color:#ba2121">&#34;rdrview&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">url</span> <span style="color:#ba2121">&#34;-T&#34;</span> <span style="color:#ba2121">&#34;title,sitename,body&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;-H&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">set-process-sentinel</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">proc</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">process</span> <span style="color:#19177c">_msg</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">status</span> (<span style="color:#00f">process-status</span> <span style="color:#19177c">process</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">code</span> (<span style="color:#00f">process-exit-status</span> <span style="color:#19177c">process</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span> ((<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;exit</span>) (<span style="color:#00f">=</span> <span style="color:#19177c">code</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">progn</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">funcall</span> <span style="color:#19177c">callback</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">with-current-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">buffer-string</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">kill-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>))) )
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">or</span> (<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;exit</span>) (<span style="color:#00f">&gt;</span> <span style="color:#19177c">code</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;signal</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">err</span> (<span style="color:#008000">with-current-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">buffer-string</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">kill-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;Error in rdrview: %s&#34;</span> <span style="color:#19177c">err</span>)))))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">proc</span>))
|
||
</span></span></code></pre></div><p>The function calls <code>callback</code> with the output of <code>rdrview</code>. This usually doesn&rsquo;t take long, but it&rsquo;s still nice to avoid freezing Emacs that way.</p>
|
||
<p>Now we have to parse the output. The <code>-T</code> flag puts the title in the <code>&lt;h1&gt;</code> tag, the site name site in the <code>&lt;h2&gt;</code> tag, and the content in a <code>&lt;div&gt;</code>. What&rsquo;s more, headers of the content are often shifted, e.g. the top-level header may well end up being and <code>&lt;h2&gt;</code> or <code>&lt;h3&gt;</code>, which does not look great in LaTeX.</p>
|
||
<p>With that said, here&rsquo;s a function that does the required changes:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/rdrview-parse</span> (<span style="color:#19177c">dom-string</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">dom</span> (<span style="color:#008000">with-temp-buffer</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> <span style="color:#19177c">dom-string</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">libxml-parse-html-region</span> (<span style="color:#00f">point-min</span>) (<span style="color:#00f">point-max</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> (<span style="color:#19177c">title</span> <span style="color:#19177c">sitename</span> <span style="color:#19177c">content</span> (<span style="color:#19177c">i</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">child</span> (<span style="color:#19177c">dom-children</span> (<span style="color:#00f">car</span> (<span style="color:#19177c">dom-by-id</span> <span style="color:#19177c">dom</span> <span style="color:#ba2121">&#34;readability-page-1&#34;</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">listp</span> <span style="color:#19177c">child</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#00f">eq</span> (<span style="color:#00f">car</span> <span style="color:#19177c">child</span>) <span style="color:#19177c">&#39;h1</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">title</span> (<span style="color:#19177c">dom-text</span> <span style="color:#19177c">child</span>)))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#00f">eq</span> (<span style="color:#00f">car</span> <span style="color:#19177c">child</span>) <span style="color:#19177c">&#39;h2</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">sitename</span> (<span style="color:#19177c">dom-text</span> <span style="color:#19177c">child</span>)))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#00f">eq</span> (<span style="color:#00f">car</span> <span style="color:#19177c">child</span>) <span style="color:#19177c">&#39;div</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">content</span> <span style="color:#19177c">child</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">while</span> (<span style="color:#008000">and</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#19177c">dom-by-tag</span> <span style="color:#19177c">content</span> <span style="color:#19177c">&#39;h1</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">dom-search</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">content</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">listp</span> <span style="color:#19177c">el</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> (<span style="color:#00f">car</span> <span style="color:#19177c">el</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;h2</span> (<span style="color:#008000">setf</span> (<span style="color:#00f">car</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">&#39;h1</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;h3</span> (<span style="color:#008000">setf</span> (<span style="color:#00f">car</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">&#39;h2</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;h4</span> (<span style="color:#008000">setf</span> (<span style="color:#00f">car</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">&#39;h3</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;h5</span> (<span style="color:#008000">setf</span> (<span style="color:#00f">car</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">&#39;h4</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;h6</span> (<span style="color:#008000">setf</span> (<span style="color:#00f">car</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">&#39;h5</span>))))))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>((<span style="color:#19177c">title</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">title</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">sitename</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">sitename</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">content</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#008000">with-temp-buffer</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">dom-print</span> <span style="color:#19177c">content</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">buffer-string</span>)))))))
|
||
</span></span></code></pre></div><h3 id="using-rdrview-from-elfeed">Using rdrview from elfeed</h3>
|
||
<p>Because I didn&rsquo;t find a smart way to advise the desired behavior into elfeed, here&rsquo;s a modification of the <code>elfeed-show-refresh--mail-style</code> function with two changes:</p>
|
||
<ul>
|
||
<li>it uses <code>rdrview</code> to fetch the HTML;</li>
|
||
<li>it saves the resulting HTML into a buffer-local variable (we&rsquo;ll need that later).</li>
|
||
</ul>
|
||
<!--listend-->
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defvar-local</span> <span style="color:#19177c">my/elfeed-show-rdrview-html</span> <span style="color:#800">nil</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/rdrview-elfeed-show</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">elfeed-show-entry</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;No elfeed entry in this buffer!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/rdrview-get</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-entry-link</span> <span style="color:#19177c">elfeed-show-entry</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">result</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">data</span> (<span style="color:#19177c">my/rdrview-parse</span> <span style="color:#19177c">result</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">inhibit-read-only</span> <span style="color:#800">t</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">title</span> (<span style="color:#19177c">elfeed-entry-title</span> <span style="color:#19177c">elfeed-show-entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">date</span> (<span style="color:#19177c">seconds-to-time</span> (<span style="color:#19177c">elfeed-entry-date</span> <span style="color:#19177c">elfeed-show-entry</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">authors</span> (<span style="color:#19177c">elfeed-meta</span> <span style="color:#19177c">elfeed-show-entry</span> <span style="color:#008000">:authors</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">link</span> (<span style="color:#19177c">elfeed-entry-link</span> <span style="color:#19177c">elfeed-show-entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">tags</span> (<span style="color:#19177c">elfeed-entry-tags</span> <span style="color:#19177c">elfeed-show-entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">tagsstr</span> (<span style="color:#00f">mapconcat</span> <span style="color:#00f">#&#39;symbol-name</span> <span style="color:#19177c">tags</span> <span style="color:#ba2121">&#34;, &#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">nicedate</span> (<span style="color:#00f">format-time-string</span> <span style="color:#ba2121">&#34;%a, %e %b %Y %T %Z&#34;</span> <span style="color:#19177c">date</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">content</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;content</span> <span style="color:#19177c">data</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">feed</span> (<span style="color:#19177c">elfeed-entry-feed</span> <span style="color:#19177c">elfeed-show-entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">feed-title</span> (<span style="color:#19177c">elfeed-feed-title</span> <span style="color:#19177c">feed</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">base</span> (<span style="color:#008000">and</span> <span style="color:#19177c">feed</span> (<span style="color:#19177c">elfeed-compute-base</span> (<span style="color:#19177c">elfeed-feed-url</span> <span style="color:#19177c">feed</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">erase-buffer</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#00f">format</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Title: %s\n&#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">propertize</span> <span style="color:#19177c">title</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-subject</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> <span style="color:#19177c">elfeed-show-entry-author</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">author</span> <span style="color:#19177c">authors</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">formatted</span> (<span style="color:#19177c">elfeed--show-format-author</span> <span style="color:#19177c">author</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">format</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Author: %s\n&#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">propertize</span> <span style="color:#19177c">formatted</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-to</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#00f">format</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Date: %s\n&#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">propertize</span> <span style="color:#19177c">nicedate</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-other</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#00f">format</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Feed: %s\n&#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">propertize</span> <span style="color:#19177c">feed-title</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-other</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> <span style="color:#19177c">tags</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#00f">format</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Tags: %s\n&#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">propertize</span> <span style="color:#19177c">tagsstr</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-other</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Link: &#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-insert-link</span> <span style="color:#19177c">link</span> <span style="color:#19177c">link</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> <span style="color:#ba2121">&#34;\n&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> <span style="color:#19177c">enclosure</span> <span style="color:#19177c">in</span> (<span style="color:#19177c">elfeed-entry-enclosures</span> <span style="color:#19177c">elfeed-show-entry</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#00f">insert</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;Enclosure: &#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;message-header-name</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#19177c">elfeed-insert-link</span> (<span style="color:#00f">car</span> <span style="color:#19177c">enclosure</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#00f">insert</span> <span style="color:#ba2121">&#34;\n&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> <span style="color:#ba2121">&#34;\n&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> <span style="color:#19177c">content</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-insert-html</span> <span style="color:#19177c">content</span> <span style="color:#19177c">base</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#00f">propertize</span> <span style="color:#ba2121">&#34;(empty)\n&#34;</span> <span style="color:#19177c">&#39;face</span> <span style="color:#19177c">&#39;italic</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">my/elfeed-show-rdrview-html</span> <span style="color:#19177c">content</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">goto-char</span> (<span style="color:#00f">point-min</span>))))))
|
||
</span></span></code></pre></div><p>That way, calling <code>M-x my/rdrview-elfeed-show</code> replaces the original content with one from <code>rdrview</code>.</p>
|
||
<h3 id="how-well-does-it-work">How well does it work?</h3>
|
||
<p>Rather ironically, it works well with sites that already ship with proper RSS, like <a href="https://protesilaos.com/">Protesilaos Stavrou&rsquo;s</a> or <a href="https://karthinks.com/software/simple-folding-with-hideshow/">Karthik Chikmagalur&rsquo;s</a> blogs or <a href="https://www.theatlantic.com/world/">The Atlantic</a> magazine.</p>
|
||
<p>Of my other subscriptions, it does a pretty good job with <a href="https://www.theverge.com/">The Verge</a>, which by default sends entries truncated by the words &ldquo;Read the full article&rdquo;. For <a href="https://arstechnica.com/">Ars Technica</a>, it works only if the story is not large enough, otherwise the site returns its HTML-based pagination interface.</p>
|
||
<p>For paywalled sites such as <a href="https://www.nytimes.com/">New York Times</a> or <a href="https://www.economist.com/">The Economist</a>, this usually doesn&rsquo;t work (by the way, what&rsquo;s the problem with providing individual RSS feeds for subscribers?). If you need this kind of thing, I&rsquo;d suggest using the <a href="https://github.com/RSS-Bridge/rss-bridge">RSS-Bridge</a> project. And if something is not available, contributing business logic there definitely makes more sense than implementing workarounds in Emacs Lisp.</p>
|
||
<h2 id="latex-and-pandoc">LaTeX and pandoc</h2>
|
||
<p>However, I also find that I&rsquo;m not really a fan of reading articles from Emacs. Somehow what works for program code doesn&rsquo;t work that well for natural text. When I have to, I usually switch the Emacs theme to a light one.</p>
|
||
<p>But the best solution I&rsquo;ve found so far is to render the required articles as PDFs. I may even print out some large articles I want to read.</p>
|
||
<h3 id="template">Template</h3>
|
||
<p>So first, we need a LaTeX template. Pandoc already ships with one, but I don&rsquo;t like it too much, so I&rsquo;ve put up a template from my LaTeX styles, targeting my preferred XeLaTeX engine.</p>
|
||
<p>I&rsquo;ll add the code here for completeness&rsquo; sake, but if you use LaTeX, you&rsquo;ll probably be better off using your own setup. Be sure to define the following variables:</p>
|
||
<ul>
|
||
<li><code>main-lang</code> and <code>other-lang</code> for polyglossia (or remove them if you have only one language)</li>
|
||
<li><code>title</code></li>
|
||
<li><code>subtitle</code></li>
|
||
<li><code>author</code></li>
|
||
<li><code>date</code></li>
|
||
</ul>
|
||
<!--listend-->
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-latex" data-lang="latex"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\documentclass</span><span style="color:#7d9029">[a4paper, 12pt]</span><span style="color:#008000">{</span>extarticle<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Math ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>amsmath<span style="color:#008000">}</span> <span style="color:#408080;font-style:italic">% Math stuff
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>amssymb<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>mathspec<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== List ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>enumitem<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>etoolbox<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setlist</span><span style="color:#008000">{</span>nosep, topsep=-10pt<span style="color:#008000">}</span> <span style="color:#408080;font-style:italic">% Remove sep-s beetween list elements
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\setlist</span><span style="color:#7d9029">[enumerate]</span><span style="color:#008000">{</span>label*=<span style="color:#008000;font-weight:bold">\arabic*</span>.<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setlist</span><span style="color:#7d9029">[enumerate,1]</span><span style="color:#008000">{</span>after=<span style="color:#008000;font-weight:bold">\vspace</span><span style="color:#008000">{</span>0.5<span style="color:#008000;font-weight:bold">\baselineskip</span><span style="color:#008000">}}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setlist</span><span style="color:#7d9029">[itemize,1]</span><span style="color:#008000">{</span>after=<span style="color:#008000;font-weight:bold">\vspace</span><span style="color:#008000">{</span>0.5<span style="color:#008000;font-weight:bold">\baselineskip</span><span style="color:#008000">}}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\AtBeginEnvironment</span><span style="color:#008000">{</span>itemize<span style="color:#008000">}{</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\setlist</span><span style="color:#7d9029">[enumerate]</span><span style="color:#008000">{</span>label=<span style="color:#008000;font-weight:bold">\arabic*</span>.<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000;font-weight:bold">\setlist</span><span style="color:#7d9029">[enumerate,1]</span><span style="color:#008000">{</span>after=<span style="color:#008000;font-weight:bold">\vspace</span><span style="color:#008000">{</span>0<span style="color:#008000;font-weight:bold">\baselineskip</span><span style="color:#008000">}}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\providecommand</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\tightlist</span><span style="color:#008000">}{</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\setlength</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\itemsep</span><span style="color:#008000">}{</span>0pt<span style="color:#008000">}</span><span style="color:#008000;font-weight:bold">\setlength</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\parskip</span><span style="color:#008000">}{</span>0pt<span style="color:#008000">}}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Link ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>xcolor<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>hyperref<span style="color:#008000">}</span> <span style="color:#408080;font-style:italic">% Links
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\hypersetup</span><span style="color:#008000">{</span>
|
||
</span></span><span style="display:flex;"><span> colorlinks=true,
|
||
</span></span><span style="display:flex;"><span> citecolor=blue,
|
||
</span></span><span style="display:flex;"><span> filecolor=blue,
|
||
</span></span><span style="display:flex;"><span> linkcolor=blue,
|
||
</span></span><span style="display:flex;"><span> urlcolor=blue,
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% Linebreaks for urls
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\expandafter\def\expandafter\UrlBreaks\expandafter</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\UrlBreaks</span><span style="color:#408080;font-style:italic">% save the current one
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\do\a\do\b\do\c\do\d\do\e\do\f\do\g\do\h\do\i\do\j</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\do\k\do\l\do\m\do\n\do\o\do\p\do\q\do\r\do\s\do\t</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\do\u\do\v\do\w\do\x\do\y\do\z\do\A\do\B\do\C\do\D</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\do\E\do\F\do\G\do\H\do\I\do\J\do\K\do\L\do\M\do\N</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\do\O\do\P\do\Q\do\R\do\S\do\T\do\U\do\V\do\W\do\X</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\do\Y\do\Z</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Table ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>array<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>booktabs<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>longtable<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>multirow<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>calc<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Images ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>graphicx<span style="color:#008000">}</span> <span style="color:#408080;font-style:italic">% Pictures
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\makeatletter</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\def\maxwidth</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\ifdim\Gin</span>@nat@width&gt;<span style="color:#008000;font-weight:bold">\linewidth\linewidth\else\Gin</span>@nat@width<span style="color:#008000;font-weight:bold">\fi</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\def\maxheight</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\ifdim\Gin</span>@nat@height&gt;<span style="color:#008000;font-weight:bold">\textheight\textheight\else\Gin</span>@nat@height<span style="color:#008000;font-weight:bold">\fi</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\makeatother</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% Scale images if necessary, so that they will not overflow the page
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% margins by default, and it is still possible to overwrite the defaults
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% using explicit options in \includegraphics[width, height, ...]{}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\setkeys</span><span style="color:#008000">{</span>Gin<span style="color:#008000">}{</span>width=<span style="color:#008000;font-weight:bold">\maxwidth</span>,height=<span style="color:#008000;font-weight:bold">\maxheight</span>,keepaspectratio<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% Set default figure placement to htbp
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\makeatletter</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\def\fps</span>@figure<span style="color:#008000">{</span>htbp<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\makeatother</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\newcommand</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\noimage</span><span style="color:#008000">}{</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\setlength</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\fboxsep</span><span style="color:#008000">}{</span>-<span style="color:#008000;font-weight:bold">\fboxrule</span><span style="color:#008000">}</span><span style="color:#408080;font-style:italic">%
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000;font-weight:bold">\fbox</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\phantom</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\rule</span><span style="color:#008000">{</span>150pt<span style="color:#008000">}{</span>100pt<span style="color:#008000">}}}</span><span style="color:#408080;font-style:italic">% Framed box
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\makeatletter</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\patchcmd</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\Gin</span>@ii<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\begingroup</span><span style="color:#008000">}</span><span style="color:#408080;font-style:italic">% &lt;search&gt;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\begingroup\renewcommand</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\@</span>latex@error<span style="color:#008000">}</span>[2]<span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\noimage</span><span style="color:#008000">}}</span><span style="color:#408080;font-style:italic">% &lt;replace&gt;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000">{}</span><span style="color:#408080;font-style:italic">% &lt;success&gt;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span> <span style="color:#008000">{}</span><span style="color:#408080;font-style:italic">% &lt;failure&gt;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\makeatother</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Misc ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>fancyvrb<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>csquotes<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#7d9029">[normalem]</span><span style="color:#008000">{</span>ulem<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% Quotes and verses style
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\AtBeginEnvironment</span><span style="color:#008000">{</span>quote<span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\singlespacing</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\AtBeginEnvironment</span><span style="color:#008000">{</span>verse<span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\singlespacing</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Text spacing ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>setspace<span style="color:#008000">}</span> <span style="color:#408080;font-style:italic">% String spacing
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\onehalfspacing</span><span style="color:#008000">{}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>indentfirst<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setlength\parindent</span><span style="color:#008000">{</span>0cm<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setlength\parskip</span><span style="color:#008000">{</span>6pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Page layout ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span>[ <span style="color:#408080;font-style:italic">% Margins
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span>left=2cm,
|
||
</span></span><span style="display:flex;"><span>right=2cm,
|
||
</span></span><span style="display:flex;"><span>top=2cm,
|
||
</span></span><span style="display:flex;"><span>bottom=2cm
|
||
</span></span><span style="display:flex;"><span>]<span style="color:#008000">{</span>geometry<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Document sectioning ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>titlesec<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titleformat*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\section</span><span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\bfseries</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titleformat*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subsection</span><span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\bfseries</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titleformat*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subsubsection</span><span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\bfseries</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titleformat*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\paragraph</span><span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\bfseries</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titleformat*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subparagraph</span><span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\bfseries\itshape</span><span style="color:#008000">}</span><span style="color:#408080;font-style:italic">% chktex 6
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titlespacing*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\section</span><span style="color:#008000">}{</span>0cm<span style="color:#008000">}{</span>12pt<span style="color:#008000">}{</span>3pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titlespacing*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subsection</span><span style="color:#008000">}{</span>0cm<span style="color:#008000">}{</span>12pt<span style="color:#008000">}{</span>3pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titlespacing*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subsubsection</span><span style="color:#008000">}{</span>0cm<span style="color:#008000">}{</span>12pt<span style="color:#008000">}{</span>0pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titlespacing*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\paragraph</span><span style="color:#008000">}{</span>0pt<span style="color:#008000">}{</span>6pt<span style="color:#008000">}{</span>6pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\titlespacing*</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subparagraph</span><span style="color:#008000">}{</span>0pt<span style="color:#008000">}{</span>6pt<span style="color:#008000">}{</span>3pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\makeatletter</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\providecommand</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\subtitle</span><span style="color:#008000">}</span>[1]<span style="color:#008000">{</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000;font-weight:bold">\apptocmd</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\@</span>title<span style="color:#008000">}{</span><span style="color:#008000;font-weight:bold">\par</span> <span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\large</span> #1 <span style="color:#008000;font-weight:bold">\par</span><span style="color:#008000">}}{}{}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\makeatother</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Pandoc =======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#ba2121">$</span><span style="color:#008000">if</span><span style="color:#666">(</span><span style="color:#008000">highlighting</span><span style="color:#666">-</span><span style="color:#008000">macros</span><span style="color:#666">)</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">highlighting</span><span style="color:#666">-</span><span style="color:#008000">macros</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">endif</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">% ====== Language ======
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"></span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>polyglossia<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setdefaultlanguage</span><span style="color:#008000">{</span><span style="color:#ba2121">$</span><span style="color:#008000">main</span><span style="color:#666">-</span><span style="color:#008000">lang</span><span style="color:#ba2121">$</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setotherlanguage</span><span style="color:#008000">{</span><span style="color:#ba2121">$</span><span style="color:#008000">other</span><span style="color:#666">-</span><span style="color:#008000">lang</span><span style="color:#ba2121">$</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\defaultfontfeatures</span><span style="color:#008000">{</span>Ligatures=<span style="color:#008000">{</span>TeX<span style="color:#008000">}}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setmainfont</span><span style="color:#008000">{</span>Open Sans<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\newfontfamily\cyrillicfont</span><span style="color:#008000">{</span>Open Sans<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setmonofont</span><span style="color:#7d9029">[Scale=0.9]</span><span style="color:#008000">{</span>DejaVu Sans Mono<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\newfontfamily</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\cyrillicfonttt</span><span style="color:#008000">}{</span>DejaVu Sans Mono<span style="color:#008000">}</span>[Scale=0.8]
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>bidi<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\usepackage</span><span style="color:#008000">{</span>microtype<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\setlength</span><span style="color:#008000">{</span><span style="color:#008000;font-weight:bold">\emergencystretch</span><span style="color:#008000">}{</span>3pt<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">if</span><span style="color:#666">(</span><span style="color:#008000">title</span><span style="color:#666">)</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\title</span><span style="color:#008000">{</span><span style="color:#ba2121">$</span><span style="color:#008000">title</span><span style="color:#ba2121">$</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">endif</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">if</span><span style="color:#666">(</span><span style="color:#008000">subtitle</span><span style="color:#666">)</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\subtitle</span><span style="color:#008000">{</span><span style="color:#ba2121">$</span><span style="color:#008000">subtitle</span><span style="color:#ba2121">$</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">endif</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">if</span><span style="color:#666">(</span><span style="color:#008000">author</span><span style="color:#666">)</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\author</span><span style="color:#008000">{</span><span style="color:#ba2121">$</span><span style="color:#008000">for</span><span style="color:#666">(</span><span style="color:#008000">author</span><span style="color:#666">)</span><span style="color:#ba2121">$$</span><span style="color:#008000">author</span><span style="color:#ba2121">$$</span><span style="color:#008000">sep</span><span style="color:#ba2121">$</span> <span style="color:#008000;font-weight:bold">\and</span> <span style="color:#ba2121">$</span><span style="color:#008000">endfor</span><span style="color:#ba2121">$</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">endif</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">if</span><span style="color:#666">(</span><span style="color:#008000">date</span><span style="color:#666">)</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\date</span><span style="color:#008000">{</span><span style="color:#ba2121">$</span><span style="color:#008000">date</span><span style="color:#ba2121">$</span><span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">endif</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\begin</span><span style="color:#008000">{</span>document<span style="color:#008000">}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\maketitle</span><span style="color:#008000">{}</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#ba2121">$</span><span style="color:#008000">body</span><span style="color:#ba2121">$</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">\end</span><span style="color:#008000">{</span>document<span style="color:#008000">}</span>
|
||
</span></span></code></pre></div><h3 id="invoking-pandoc">Invoking pandoc</h3>
|
||
<p>Now that we have the template, let&rsquo;s save it somewhere and store the path to a variable:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/rdrview-template</span> (<span style="color:#00f">expand-file-name</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span> <span style="color:#19177c">user-emacs-directory</span> <span style="color:#ba2121">&#34;rdrview.tex&#34;</span>)))
|
||
</span></span></code></pre></div><p>And let&rsquo;s invoke pandoc. We need to pass the following flags:</p>
|
||
<ul>
|
||
<li><code>--pdf-engine=xelatex</code>, of course</li>
|
||
<li><code>--template &lt;path-to-template&gt;</code>;</li>
|
||
<li><code>-o &lt;path-to-pdf&gt;</code>;</li>
|
||
<li><code>--variable key=value</code>.</li>
|
||
</ul>
|
||
<p>In fact, pandoc is a pretty awesome tool in the sense that it allows for feeding custom variables to rich-language templates.</p>
|
||
<p>So, the rendering function is as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">cl-defun</span> <span style="color:#19177c">my/rdrview-render</span> (<span style="color:#19177c">content</span> <span style="color:#19177c">type</span> <span style="color:#19177c">variables</span> <span style="color:#19177c">callback</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">&amp;key</span> <span style="color:#19177c">file-name</span> <span style="color:#19177c">overwrite</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Render CONTENT with pandoc.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">TYPE is a file extension as supported by pandoc, for instance,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">html or txt. VARIABLES is an alist that is fed into the
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">template. After the rendering is complete successfully, CALLBACK
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">is called with the resulting PDF.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">FILE-NAME is a path to the resulting PDF. If nil it&#39;s generated
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">randomly.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">If a file with the given FILE-NAME already exists, the function will
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">invoke CALLBACK straight away without doing the rendering, unless
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">OVERWRITE is non-nil.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">file-name</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">file-name</span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;/tmp/%d.pdf&#34;</span> (<span style="color:#00f">random</span> <span style="color:#666">100000000</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> (<span style="color:#19177c">params</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">temp-file-name</span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;/tmp/%d.%s&#34;</span> (<span style="color:#00f">random</span> <span style="color:#666">100000000</span>) <span style="color:#19177c">type</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> (<span style="color:#19177c">key</span> <span style="color:#666">.</span> <span style="color:#19177c">value</span>) <span style="color:#19177c">in</span> <span style="color:#19177c">variables</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">when</span> <span style="color:#19177c">value</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#008000">progn</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">push</span> <span style="color:#ba2121">&#34;--variable&#34;</span> <span style="color:#19177c">params</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">push</span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;%s=%s&#34;</span> <span style="color:#19177c">key</span> <span style="color:#19177c">value</span>) <span style="color:#19177c">params</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">params</span> (<span style="color:#00f">nreverse</span> <span style="color:#19177c">params</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#008000">and</span> (<span style="color:#00f">file-exists-p</span> <span style="color:#19177c">file-name</span>) (<span style="color:#19177c">not</span> <span style="color:#19177c">overwrite</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">funcall</span> <span style="color:#19177c">callback</span> <span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">with-temp-file</span> <span style="color:#19177c">temp-file-name</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> <span style="color:#19177c">content</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">proc</span> (<span style="color:#00f">apply</span> <span style="color:#00f">#&#39;start-process</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;pandoc&#34;</span> (<span style="color:#00f">get-buffer-create</span> <span style="color:#ba2121">&#34;*Pandoc*&#34;</span>) <span style="color:#ba2121">&#34;pandoc&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">temp-file-name</span> <span style="color:#ba2121">&#34;-o&#34;</span> <span style="color:#19177c">file-name</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;--pdf-engine=xelatex&#34;</span> <span style="color:#ba2121">&#34;--template&#34;</span> <span style="color:#19177c">my/rdrview-template</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">params</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">set-process-sentinel</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">proc</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">process</span> <span style="color:#19177c">_msg</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">status</span> (<span style="color:#00f">process-status</span> <span style="color:#19177c">process</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">code</span> (<span style="color:#00f">process-exit-status</span> <span style="color:#19177c">process</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span> ((<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;exit</span>) (<span style="color:#00f">=</span> <span style="color:#19177c">code</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">progn</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">message</span> <span style="color:#ba2121">&#34;Done!&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">funcall</span> <span style="color:#19177c">callback</span> <span style="color:#19177c">file-name</span>)))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">or</span> (<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;exit</span>) (<span style="color:#00f">&gt;</span> <span style="color:#19177c">code</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;signal</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;Error in pandoc. Check the *Pandoc* buffer&#34;</span>)))))))))
|
||
</span></span></code></pre></div><h3 id="opening-elfeed-entries">Opening elfeed entries</h3>
|
||
<p>Now we have everything required to open elfeed entries.</p>
|
||
<p>Also, in my case elfeed entries come in two languages, so I have to set <code>main-lang</code> and <code>other-lang</code> variables accordingly. Here&rsquo;s the main function:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/elfeed-pdf-dir</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/.elfeed/pdf/&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/elfeed-open-pdf</span> (<span style="color:#19177c">entry</span> <span style="color:#19177c">overwrite</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Open the current elfeed ENTRY with a pdf viewer.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">If OVERWRITE is non-nil, do the rendering even if the resulting
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">PDF already exists.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span> (<span style="color:#00f">list</span> <span style="color:#19177c">elfeed-show-entry</span> <span style="color:#19177c">current-prefix-arg</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">authors</span> (<span style="color:#00f">mapcar</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">m</span>) (<span style="color:#00f">plist-get</span> <span style="color:#19177c">m</span> <span style="color:#008000">:name</span>)) (<span style="color:#19177c">elfeed-meta</span> <span style="color:#19177c">entry</span> <span style="color:#008000">:authors</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">feed-title</span> (<span style="color:#19177c">elfeed-feed-title</span> (<span style="color:#19177c">elfeed-entry-feed</span> <span style="color:#19177c">entry</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">tags</span> (<span style="color:#00f">mapconcat</span> <span style="color:#00f">#&#39;symbol-name</span> (<span style="color:#19177c">elfeed-entry-tags</span> <span style="color:#19177c">entry</span>) <span style="color:#ba2121">&#34;, &#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">date</span> (<span style="color:#00f">format-time-string</span> <span style="color:#ba2121">&#34;%a, %e %b %Y&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seconds-to-time</span> (<span style="color:#19177c">elfeed-entry-date</span> <span style="color:#19177c">entry</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">content</span> (<span style="color:#19177c">elfeed-deref</span> (<span style="color:#19177c">elfeed-entry-content</span> <span style="color:#19177c">entry</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">file-name</span> (<span style="color:#00f">concat</span> <span style="color:#19177c">my/elfeed-pdf-dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-ref-id</span> (<span style="color:#19177c">elfeed-entry-content</span> <span style="color:#19177c">entry</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;.pdf&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">main-language</span> <span style="color:#ba2121">&#34;english&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-language</span> <span style="color:#ba2121">&#34;russian&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">content</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;No content!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">subtitle</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#19177c">seq-empty-p</span> <span style="color:#19177c">authors</span>) <span style="color:#19177c">feed-title</span>)
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">and</span> (<span style="color:#19177c">not</span> (<span style="color:#19177c">seq-empty-p</span> (<span style="color:#00f">car</span> <span style="color:#19177c">authors</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-match-p</span> (<span style="color:#00f">regexp-quote</span> (<span style="color:#00f">car</span> <span style="color:#19177c">authors</span>)) <span style="color:#19177c">feed-title</span>)) <span style="color:#19177c">feed-title</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#800">t</span> (<span style="color:#00f">concat</span> (<span style="color:#19177c">string-join</span> <span style="color:#19177c">authors</span> <span style="color:#ba2121">&#34;, &#34;</span>) <span style="color:#ba2121">&#34;\\\\&#34;</span> <span style="color:#19177c">feed-title</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">member</span> <span style="color:#19177c">&#39;ru</span> (<span style="color:#19177c">elfeed-entry-tags</span> <span style="color:#19177c">entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">main-language</span> <span style="color:#ba2121">&#34;russian&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">other-language</span> <span style="color:#ba2121">&#34;english&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/rdrview-render</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#19177c">bound-and-true-p</span> <span style="color:#19177c">my/elfeed-show-rdrview-html</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">my/elfeed-show-rdrview-html</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">content</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-entry-content-type</span> <span style="color:#19177c">entry</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>((<span style="color:#19177c">title</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#19177c">elfeed-entry-title</span> <span style="color:#19177c">entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">subtitle</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">subtitle</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">date</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">date</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">tags</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">tags</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">main-lang</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">main-language</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-lang</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">other-language</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;xdg-open&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;xdg-open&#34;</span> <span style="color:#19177c">file-name</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:file-name</span> <span style="color:#19177c">file-name</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:overwrite</span> <span style="color:#19177c">current-prefix-arg</span>)))
|
||
</span></span></code></pre></div><p>If the <code>my/elfeed-show-rdrview-html</code> variable is bound and true, then the content in this buffer was retrieved via <code>rdrview</code>, so we&rsquo;ll use that instead of the output of <code>elfeed-deref</code>.</p>
|
||
<p>Now we can open elfeed entries in a PDF viewer, which I find much nicer to read. Given that RSS feeds generally ship with simpler HTML than the regular websites, results usually look awesome:</p>
|
||
<figure><img src="https://sqrtminusone.xyz/images/pdf-prot.png"/>
|
||
</figure>
|
||
|
||
<h3 id="opening-arbitrary-sites">Opening arbitrary sites</h3>
|
||
<p>As you may have noticed, we also can display arbitrary web pages with this setup, so let&rsquo;s go ahead and implement that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/get-languages</span> (<span style="color:#19177c">url</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">main-lang</span> <span style="color:#ba2121">&#34;english&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-lang</span> <span style="color:#ba2121">&#34;russian&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#19177c">string-match-p</span> (<span style="color:#008000">rx</span> <span style="color:#ba2121">&#34;.ru&#34;</span>) <span style="color:#19177c">url</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">main-lang</span> <span style="color:#ba2121">&#34;russian&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">other-lang</span> <span style="color:#ba2121">&#34;english&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">list</span> <span style="color:#19177c">main-lang</span> <span style="color:#19177c">other-lang</span>)))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/rdrview-open</span> (<span style="color:#19177c">url</span> <span style="color:#19177c">overwrite</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">url</span> (<span style="color:#00f">read-from-minibuffer</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;URL: &#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#19177c">bound-and-true-p</span> <span style="color:#19177c">elfeed-show-entry</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-entry-link</span> <span style="color:#19177c">elfeed-show-entry</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#19177c">string-empty-p</span> <span style="color:#19177c">url</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;URL is empty&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">list</span> <span style="color:#19177c">url</span> <span style="color:#19177c">current-prefix-arg</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/rdrview-get</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">url</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">res</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">data</span> (<span style="color:#19177c">my/rdrview-parse</span> <span style="color:#19177c">res</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">langs</span> (<span style="color:#19177c">my/get-languages</span> <span style="color:#19177c">url</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/rdrview-render</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;content</span> <span style="color:#19177c">data</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;html</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>((<span style="color:#19177c">title</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;title</span> <span style="color:#19177c">data</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">subtitle</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;sitename</span> <span style="color:#19177c">data</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">main-lang</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">langs</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-lang</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">langs</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;xdg-open&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;xdg-open&#34;</span> <span style="color:#19177c">file-name</span>)))))))
|
||
</span></span></code></pre></div><p>Unfortunately, this part doesn&rsquo;t work that well, so we can&rsquo;t just uninstall Firefox or Chromium and browse the web from a PDF viewer.</p>
|
||
<p>The most common problem I&rsquo;ve encountered is incorrectly formed pictures, such as <code>.png</code> files without the boundary info. I&rsquo;m sure you&rsquo;ve also come across this if you ever tried to insert a lot of Internet pictures into a LaTeX document.</p>
|
||
<p>However, sans the pictures issue, for certain sites like Wikipedia this is usable. For instance, here&rsquo;s how the Emacs page looks:
|
||
<img src="https://sqrtminusone.xyz/images/pdf-emacs.png" alt=""></p>
|
||
<h2 id="youtube-transcripts">YouTube transcripts</h2>
|
||
<h3 id="getting-subtitles">Getting subtitles</h3>
|
||
<p>Finally, let&rsquo;s get to transcripts.</p>
|
||
<p>In principle, the YouTube API allows for downloading subtitles, but I&rsquo;ve found <a href="https://github.com/jdepoix/youtube-transcript-api">this awesome Python script</a> which does the same. You can install it from <code>pip</code>, or here&rsquo;s mine <a href="https://github.com/SqrtMinusOne/channel-q/blob/master/youtube-transcript-api.scm">Guix definition</a> once again.</p>
|
||
<p>Much like the previous cases, we need to invoke the program and save the output. The <a href="https://en.wikipedia.org/wiki/WebVTT">WebVTT</a> format will work well enough for our purposes. Here comes the function:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">cl-defun</span> <span style="color:#19177c">my/youtube-subtitles-get</span> (<span style="color:#19177c">video-id</span> <span style="color:#19177c">callback</span> <span style="color:#008000">&amp;key</span> <span style="color:#19177c">file-name</span> <span style="color:#19177c">overwrite</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Get subtitles for VIDEO-ID in WebVTT format.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">Call CALLBACK when done.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">FILE-NAME is a path to the resulting WebVTT file. If nil it&#39;s
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">generated randomly.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">If a file with the given FILE-NAME already exists, the function will
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">invoke CALLBACK straight away without doing the rendering, unless
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">OVERWRITE is non-nil.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">file-name</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">file-name</span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;/tmp/%d.vtt&#34;</span> (<span style="color:#00f">random</span> <span style="color:#666">100000000</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#008000">and</span> (<span style="color:#00f">file-exists-p</span> <span style="color:#19177c">file-name</span>) (<span style="color:#19177c">not</span> <span style="color:#19177c">overwrite</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">funcall</span> <span style="color:#19177c">callback</span> <span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">buffer</span> (<span style="color:#19177c">generate-new-buffer</span> <span style="color:#ba2121">&#34;youtube-transcripts&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">proc</span> (<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;youtube_transcript_api&#34;</span> <span style="color:#19177c">buffer</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;youtube_transcript_api&#34;</span> <span style="color:#19177c">video-id</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;--languages&#34;</span> <span style="color:#ba2121">&#34;en&#34;</span> <span style="color:#ba2121">&#34;ru&#34;</span> <span style="color:#ba2121">&#34;de&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;--format&#34;</span> <span style="color:#ba2121">&#34;webvtt&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">set-process-sentinel</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">proc</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">process</span> <span style="color:#19177c">_msg</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">status</span> (<span style="color:#00f">process-status</span> <span style="color:#19177c">process</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">code</span> (<span style="color:#00f">process-exit-status</span> <span style="color:#19177c">process</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span> ((<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;exit</span>) (<span style="color:#00f">=</span> <span style="color:#19177c">code</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">progn</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">with-current-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#00f">buffer-file-name</span> <span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">save-buffer</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">kill-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">funcall</span> <span style="color:#19177c">callback</span> <span style="color:#19177c">file-name</span>)))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">or</span> (<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;exit</span>) (<span style="color:#00f">&gt;</span> <span style="color:#19177c">code</span> <span style="color:#666">0</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">eq</span> <span style="color:#19177c">status</span> <span style="color:#19177c">&#39;signal</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">err</span> (<span style="color:#008000">with-current-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">buffer-string</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">kill-buffer</span> (<span style="color:#00f">process-buffer</span> <span style="color:#19177c">process</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;Error in youtube_transcript_api: %s&#34;</span> <span style="color:#19177c">err</span>)))))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">proc</span>)))
|
||
</span></span></code></pre></div><h3 id="elfeed-and-subed">elfeed and subed</h3>
|
||
<p>Now that we have a standalone function, let&rsquo;s invoke it with the current <code>elfeed-show-entry</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/elfeed-srt-dir</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/.elfeed/srt/&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/elfeed-youtube-subtitles</span> (<span style="color:#19177c">entry</span> <span style="color:#008000">&amp;optional</span> <span style="color:#19177c">arg</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Get subtitles for the current elfeed ENTRY.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">Works only in the entry is a YouTube video.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">If ARG is non-nil, re-fetch the subtitles regardless of whether
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">they were fetched before.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span> (<span style="color:#00f">list</span> <span style="color:#19177c">elfeed-show-entry</span> <span style="color:#19177c">current-prefix-arg</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">video-id</span> (<span style="color:#19177c">cadr</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">assoc</span> <span style="color:#ba2121">&#34;watch?v&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">url-parse-query-string</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">substring</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">url-filename</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">url-generic-parse-url</span> (<span style="color:#19177c">elfeed-entry-link</span> <span style="color:#19177c">entry</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">1</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">video-id</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;Can&#39;t get video ID from the entry&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/youtube-subtitles-get</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">video-id</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">with-current-buffer</span> (<span style="color:#19177c">find-file-other-window</span> <span style="color:#19177c">file-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">elfeed-show-entry</span> <span style="color:#19177c">entry</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">goto-char</span> (<span style="color:#00f">point-min</span>))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:file-name</span> (<span style="color:#00f">concat</span> <span style="color:#19177c">my/elfeed-srt-dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-ref-id</span> (<span style="color:#19177c">elfeed-entry-content</span> <span style="color:#19177c">entry</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;.vtt&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:overwrite</span> <span style="color:#19177c">arg</span>)))
|
||
</span></span></code></pre></div><p>That opens up a <code>.vtt</code> buffer with the subtitles for the current video, which means now we can use the functionality of Sacha Chua&rsquo;s awesome package called <a href="https://github.com/sachac/subed">subed</a>.</p>
|
||
<p>This package, besides syntax highlighting, allows for controlling the MPV playback, for instance by moving the cursor in the subtitles buffer. Using that requires having the URL of the video in this buffer, which necessitates the line with <code>setq-local</code> in the previous function.</p>
|
||
<p>Also, the package launches its own instance of MPV to control it via JSON-IPC, so there seems to be no easy way to integrate it with EMMS. But at least I can reuse the <code>emms-player-mpv-parameters</code> variable, the method of setting which I&rsquo;ve discussed in a <a href="https://sqrtminusone.xyz/posts/2021-09-07-emms/">previous blog post</a>. The function is as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/subed-elfeed</span> (<span style="color:#19177c">entry</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Open the video file from elfeed ENTRY in MPV.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">This has to be launched from inside the subtitles buffer, opened
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">by the </span><span style="color:#19177c">`my/elfeed-youtube-subtitles&#39;</span><span style="color:#ba2121"> function.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span> (<span style="color:#00f">list</span> <span style="color:#19177c">elfeed-show-entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">entry</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;No entry!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> (<span style="color:#19177c">derived-mode-p</span> <span style="color:#19177c">&#39;subed-mode</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;Not subed mode!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">subed-mpv-arguments</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-uniq</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">append</span> <span style="color:#19177c">subed-mpv-arguments</span> <span style="color:#19177c">emms-player-mpv-parameters</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">subed-mpv-video-file</span> (<span style="color:#19177c">elfeed-entry-link</span> <span style="color:#19177c">entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">subed-mpv--play</span> <span style="color:#19177c">subed-mpv-video-file</span>))
|
||
</span></span></code></pre></div><p>And here&rsquo;s how it looks when used (the video on the screenshot is <a href="https://www.youtube.com/watch?v=qjAIXCmhCQQ">this System Crafters&rsquo; stream</a>):
|
||
<img src="https://sqrtminusone.xyz/images/pdf-subed.png" alt=""></p>
|
||
<p>Keep in mind that this function has to be launched inside the buffer opened by the <code>my/elfeed-youtube-subtitles</code> function.</p>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>A few cases of literate configuration</title>
|
||
<link>https://sqrtminusone.xyz/posts/2022-02-12-literate/</link>
|
||
<pubDate>Sat, 12 Feb 2022 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2022-02-12-literate/</guid>
|
||
<content type="html">
|
||
<p>A post that arose from the discussion of literate configuration on the <a href="https://systemcrafters.net/">System Crafters</a> Discord.</p>
|
||
<p>I am using the <a href="https://leanpub.com/lit-config">literate configuration</a> strategy (based on <a href="https://orgmode.org/">Emacs&rsquo; Org Mode</a>) to manage most of my configuration files. A piece of such a configuration can be as simple as an Org file, which is tangled to one or many plain-text configuration files, but it can be more.</p>
|
||
<p>In my opinion, a literate configuration can be more straightforward and concise than a &ldquo;normal&rdquo; one, thanks to Org Mode&rsquo;s capabilities of <a href="https://orgmode.org/manual/Working-with-Source-Code.html">working with source code</a>. So here I present a few examples from my configuration where I think this is the case:</p>
|
||
<ul>
|
||
<li>Managing system colors</li>
|
||
<li>Managing manifests for Guix profiles</li>
|
||
<li>Configuring modules in polybar</li>
|
||
</ul>
|
||
<p>I hope you find something interesting here!</p>
|
||
<h2 id="colors">Colors</h2>
|
||
<p>Let&rsquo;s start with system colors.</p>
|
||
<p>My favorite color theme is Palenight (<a href="https://github.com/JonathanSpeek/palenight-iterm2">color codes</a>), and I want to have one source of truth for these colors. Except for Emacs itself, which has <a href="https://github.com/doomemacs/themes#theme-list">doom-palenight</a> (and in which I occasionally switch to <code>doom-one-light</code>, e.g. when reading a long text), it can be done rather nicely with Org Mode.</p>
|
||
<p>First, let&rsquo;s define a table with all the color codes:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+tblname: colors
|
||
</span></span><span style="display:flex;"><span>| color | key | value |
|
||
</span></span><span style="display:flex;"><span>|---------------+---------+---------|
|
||
</span></span><span style="display:flex;"><span>| black | color0 | #292d3e |
|
||
</span></span><span style="display:flex;"><span>| red | color1 | #f07178 |
|
||
</span></span><span style="display:flex;"><span>| green | color2 | #c3e88d |
|
||
</span></span><span style="display:flex;"><span>| yellow | color3 | #ffcb6b |
|
||
</span></span><span style="display:flex;"><span>| blue | color4 | #82aaff |
|
||
</span></span><span style="display:flex;"><span>| magenta | color5 | #c792ea |
|
||
</span></span><span style="display:flex;"><span>| cyan | color6 | #89ddff |
|
||
</span></span><span style="display:flex;"><span>| white | color7 | #d0d0d0 |
|
||
</span></span><span style="display:flex;"><span>| light-black | color8 | #434758 |
|
||
</span></span><span style="display:flex;"><span>| light-red | color9 | #ff8b92 |
|
||
</span></span><span style="display:flex;"><span>| light-green | color10 | #ddffa7 |
|
||
</span></span><span style="display:flex;"><span>| light-yellow | color11 | #ffe585 |
|
||
</span></span><span style="display:flex;"><span>| light-blue | color12 | #9cc4ff |
|
||
</span></span><span style="display:flex;"><span>| light-magenta | color13 | #e1acff |
|
||
</span></span><span style="display:flex;"><span>| light-cyan | color14 | #a3f7ff |
|
||
</span></span><span style="display:flex;"><span>| light-white | color15 | #ffffff |
|
||
</span></span><span style="display:flex;"><span>| color-fg | | #000000 |
|
||
</span></span></code></pre></div><p>Contents of this table can then be <a href="https://orgmode.org/manual/Environment-of-a-Code-Block.html">accessed from a code block</a>. Let&rsquo;s define one to return the color code based on its name:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: get-color
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :var table=colors name=&#34;black&#34; quote=0
|
||
</span></span><span style="display:flex;"><span>(let ((color (seq-some (lambda (e) (and (string= name (car e)) (nth 2 e))) table)))
|
||
</span></span><span style="display:flex;"><span> (if (&gt; quote 0)
|
||
</span></span><span style="display:flex;"><span> (concat &#34;\&#34;&#34; color &#34;\&#34;&#34;)
|
||
</span></span><span style="display:flex;"><span> color))
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>Evaluating this block of code should return color code, corresponding to &ldquo;black&rdquo;.</p>
|
||
<p>And the best part is that the results of evaluation of one code block can be included to others with <a href="https://orgmode.org/manual/Noweb-Reference-Syntax.html">noweb</a>. For instance, here&rsquo;s my <a href="https://pwmt.org/projects/zathura/">zathura</a> config:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src conf-space :noweb yes :tangle .config/zathura/zathurarc
|
||
</span></span><span style="display:flex;"><span>set abort-clear-search false
|
||
</span></span><span style="display:flex;"><span>set guioptions cs
|
||
</span></span><span style="display:flex;"><span>set selection-clipboard clipboard
|
||
</span></span><span style="display:flex;"><span>set recolor true
|
||
</span></span><span style="display:flex;"><span>map &lt;C-r&gt; set recolor false
|
||
</span></span><span style="display:flex;"><span>map &lt;C-R&gt; set recolor true
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>set recolor-lightcolor &lt;&lt;get-color(name=&#34;black&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>set completion-bg &lt;&lt;get-color(name=&#34;black&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set completion-fg &lt;&lt;get-color(name=&#34;white&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set completion-group-bg &lt;&lt;get-color(name=&#34;light-black&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set completion-group-fg &lt;&lt;get-color(name=&#34;white&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set completion-highlight-bg &lt;&lt;get-color(name=&#34;magenta&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set completion-highlight-fg &lt;&lt;get-color(name=&#34;black&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>set inputbar-bg &lt;&lt;get-color(name=&#34;black&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set inputbar-fg &lt;&lt;get-color(name=&#34;light-magenta&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set statusbar-bg &lt;&lt;get-color(name=&#34;black&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set statusbar-fg &lt;&lt;get-color(name=&#34;light-magenta&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>set notification-error-bg &lt;&lt;get-color(name=&#34;red&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set notification-error-fg &lt;&lt;get-color(name=&#34;color-fg&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set notification-warning-bg &lt;&lt;get-color(name=&#34;yellow&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>set notification-warning-fg &lt;&lt;get-color(name=&#34;color-fg&#34;, quote=1)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>Running <code>M-x org-babel-expand-src-block</code> (<code>C-c C-v v</code>) on this code block will open the code buffer with noweb expressions expanded, for instance the line with <code>set recolor-lightcolor</code> will look like:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>set recolor-lightcolor &#34;#292d3e&#34;
|
||
</span></span></code></pre></div><p><code>M-x org-babel-tangle</code> (<code>C-c C-v t</code>) will also produce <code>zathurarc</code> with the colors set (given that there&rsquo;s <code>:noweb yes</code> somewhere in the code block configuration).</p>
|
||
<p>One note is that by default running these commands will require the user to confirm evaluation of each code block. To avoid that, you can set <code>org-confirm-babel-evaluate</code> to <code>nil</code>, for example:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/org-config-files</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;/home/pavel/Emacs.org&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;/home/pavel/Desktop.org&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;/home/pavel/Console.org&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;/home/pavel/Guix.org&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;/home/pavel/Mail.org&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;org-mode-hook</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">member</span> (<span style="color:#00f">buffer-file-name</span>) <span style="color:#19177c">my/org-config-files</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">org-confirm-babel-evaluate</span> <span style="color:#800">nil</span>))))
|
||
</span></span></code></pre></div><p>And, to close the loop on colors, let&rsquo;s generate <code>.Xresources</code> from that table:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: get-xresources
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :var table=colors
|
||
</span></span><span style="display:flex;"><span>(mapconcat
|
||
</span></span><span style="display:flex;"><span> (lambda (elem)
|
||
</span></span><span style="display:flex;"><span> (concat &#34;*&#34; (nth 1 elem) &#34;: &#34; (nth 2 elem)))
|
||
</span></span><span style="display:flex;"><span> (seq-filter
|
||
</span></span><span style="display:flex;"><span> (lambda (elem) (and (nth 1 elem)
|
||
</span></span><span style="display:flex;"><span> (not (string-empty-p (nth 1 elem)))))
|
||
</span></span><span style="display:flex;"><span> table)
|
||
</span></span><span style="display:flex;"><span> &#34;\n&#34;)
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+begin_src conf-xdefaults :noweb yes :tangle ~/.Xresources
|
||
</span></span><span style="display:flex;"><span>&lt;&lt;get-xresources()&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>*background: &lt;&lt;get-color(name=&#34;black&#34;)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>*foreground: &lt;&lt;get-color(name=&#34;white&#34;)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>So, whenever a program is capable of reading <code>.Xresources</code>, it will get colors from there, otherwise, it will get colors from noweb expressions in the literate config. Thus, in both cases, the color is set in a single Org Mode table.</p>
|
||
<h2 id="guix-dependencies">Guix dependencies</h2>
|
||
<p>Another case I want to cover is <a href="https://guix.gnu.org/en/cookbook/en/html_node/Advanced-package-management.html#Advanced-package-management">using profiles in GNU Guix</a>.</p>
|
||
<p>A &ldquo;profile&rdquo; in Guix is a way to group package installations. For instance, I have a &ldquo;music&rdquo; profile that has software like <a href="https://www.musicpd.org/">MPD</a>, <a href="https://github.com/ncmpcpp/ncmpcpp">ncmpcpp</a> that I&rsquo;m still occasionally using because of its tag editor, etc. Corresponding to that profile, there&rsquo;s a manifest named <code>music.scm</code> that looks like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scheme" data-lang="scheme"><span style="display:flex;"><span>(<span style="color:#00f">specifications-&gt;manifest</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;flac&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;cuetools&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;shntool&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;mpd-mpc&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;mpd-watcher&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;picard&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;ncmpcpp&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;mpd&#34;</span>))
|
||
</span></span></code></pre></div><p>I could generate this file with <code>org-babel</code> as any other, but that is often not so convenient. For example, I have a <a href="https://github.com/polybar/polybar">polybar</a> module that uses <a href="https://github.com/risacher/sunwait">sunwait</a> to show sunset and sunrise times, and ideally, I want to declare <code>sunwait</code> to be in the &ldquo;desktop-polybar&rdquo; profile in the same section that has the polybar module definition and the bash script.</p>
|
||
<p>So here&rsquo;s an approach I came up with. The relevant section of the config looks like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>*** sun
|
||
</span></span><span style="display:flex;"><span>| Category | Guix dependency |
|
||
</span></span><span style="display:flex;"><span>|-----------------+-----------------|
|
||
</span></span><span style="display:flex;"><span>| desktop-polybar | sunwait |
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>Prints out the time of sunrise/sunset. Uses [[https://github.com/risacher/sunwait][sunwait]]
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+begin_src bash :tangle ./bin/polybar/sun.sh :noweb yes
|
||
</span></span><span style="display:flex;"><span>...script...
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+begin_src conf-windows :noweb yes
|
||
</span></span><span style="display:flex;"><span>...polybar module definition...
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p><code>sunwait</code> is declared in an Org table that looks like that:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Category</th>
|
||
<th>Guix dependency</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>desktop-polybar</td>
|
||
<td>sunwait</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Such tables are spread through my <code>Desktop.org</code>, for instance, here is another one for a polybar module that depends on dateutils:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Category</th>
|
||
<th>Guix dependency</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>desktop-polybar</td>
|
||
<td>dateutils</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Thus I made a function that extracts packages from all such tables from the current Org buffer. The rules are as follows:</p>
|
||
<ul>
|
||
<li>If a column name matches <code>[G|g]uix.*dep</code>, its contents are added to the result.</li>
|
||
<li>If <code>CATEGORY</code> is passed, a column with name <code>[C|c]ategory</code> is used to filter results. That way, one Org file can be used to produce multiple manifests.</li>
|
||
<li>If <code>CATEGORY</code> is not passed, entries with the non-empty category are filtered out</li>
|
||
<li>If there is a <code>[D|d]isabled</code> column, entries that have a non-empty value in this column are filtered out.</li>
|
||
</ul>
|
||
<p>And here is the implementation:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/extract-guix-dependencies</span> (<span style="color:#008000">&amp;optional</span> <span style="color:#19177c">category</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">dependencies</span> <span style="color:#666">&#39;</span>()))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">org-table-map-tables</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">q</span>) (<span style="color:#19177c">not</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">q</span> <span style="color:#19177c">&#39;hline</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">org-table-to-lisp</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">dep-name-index</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-position</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapcar</span> <span style="color:#00f">#&#39;substring-no-properties</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">table</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:test</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">_</span> <span style="color:#19177c">elem</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-match-p</span> <span style="color:#ba2121">&#34;[G|g]uix.*dep&#34;</span> <span style="color:#19177c">elem</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">category-name-index</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-position</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapcar</span> <span style="color:#00f">#&#39;substring-no-properties</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">table</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:test</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">_</span> <span style="color:#19177c">elem</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-match-p</span> <span style="color:#ba2121">&#34;.*[C|c]ategory.*&#34;</span> <span style="color:#19177c">elem</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">disabled-name-index</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-position</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapcar</span> <span style="color:#00f">#&#39;substring-no-properties</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">table</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:test</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">_</span> <span style="color:#19177c">elem</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-match-p</span> <span style="color:#ba2121">&#34;.*[D|d]isabled.*&#34;</span> <span style="color:#19177c">elem</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> <span style="color:#19177c">dep-name-index</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">elem</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">table</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">and</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Category</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">or</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Category is not set and not present in the table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">and</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">or</span> (<span style="color:#19177c">not</span> <span style="color:#19177c">category</span>) (<span style="color:#19177c">string-empty-p</span> <span style="color:#19177c">category</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> <span style="color:#19177c">category-name-index</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Category is set and present in the table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">and</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">category-name-index</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#19177c">string-empty-p</span> <span style="color:#19177c">category</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-match-p</span> <span style="color:#19177c">category</span> (<span style="color:#00f">nth</span> <span style="color:#19177c">category-name-index</span> <span style="color:#19177c">elem</span>))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Not disabled</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">or</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> <span style="color:#19177c">disabled-name-index</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-empty-p</span> (<span style="color:#00f">nth</span> <span style="color:#19177c">disabled-name-index</span> <span style="color:#19177c">elem</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">add-to-list</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;dependencies</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">substring-no-properties</span> (<span style="color:#00f">nth</span> <span style="color:#19177c">dep-name-index</span> <span style="color:#19177c">elem</span>)))))))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">dependencies</span>))
|
||
</span></span></code></pre></div><p>Let&rsquo;s execute this function in the current buffer:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">my/extract-guix-dependencies</span> <span style="color:#ba2121">&#34;desktop-polybar&#34;</span>)
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#ba2121">&#34;dateutils&#34;</span> <span style="color:#ba2121">&#34;sunwait&#34;</span>)
|
||
</span></span></code></pre></div><p>As expected, it found both <code>dateutils</code> and <code>sunwait</code>. To make it work in the configuration, it is necessary to format the list so that Scheme could read it:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/format-guix-dependencies</span> (<span style="color:#008000">&amp;optional</span> <span style="color:#19177c">category</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapconcat</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">e</span>) (<span style="color:#00f">concat</span> <span style="color:#ba2121">&#34;\&#34;&#34;</span> <span style="color:#19177c">e</span> <span style="color:#ba2121">&#34;\&#34;&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/extract-guix-dependencies</span> <span style="color:#19177c">category</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;\n&#34;</span>))
|
||
</span></span></code></pre></div><p>And we need an Org snippet such as this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: packages
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :tangle no :var category=&#34;&#34;
|
||
</span></span><span style="display:flex;"><span>(my/format-guix-dependencies category)
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>Now, creating a manifest for the <code>desktop-polybar</code> profile is as simple as:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src scheme :tangle ~/.config/guix/manifests/desktop-polybar.scm :noweb yes
|
||
</span></span><span style="display:flex;"><span>(specifications-&gt;manifest
|
||
</span></span><span style="display:flex;"><span> &#39;(
|
||
</span></span><span style="display:flex;"><span> &lt;&lt;packages(&#34;desktop-polybar&#34;)&gt;&gt;))
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>There&rsquo;s a newline symbol between &ldquo;(&rdquo; and <code>&lt;&lt;packages(&quot;desktop-polybar&quot;)&gt;&gt;</code> because whenever a noweb expression expands into multiple lines, for each new line noweb duplicates contents between the start of the line and the start of the expression.</p>
|
||
<p>One reason this is so is to support languages where indentation is a part of the syntax, for instance, Python:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">class</span> <span style="color:#00f;font-weight:bold">TestClass</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&lt;&lt;</span>class<span style="color:#666">-</span>contents<span style="color:#666">&gt;&gt;</span>
|
||
</span></span></code></pre></div><p>So every line of <code>&lt;&lt;class-contents&gt;&gt;</code> will be indented appropriately. In our case though, it is a minor inconvenience to be aware of.</p>
|
||
<h2 id="polybar">Polybar</h2>
|
||
<p>Now, the most <del>crazy</del> advanced case I&rsquo;ve come up with so far.</p>
|
||
<p>Basically, here is how my <a href="https://github.com/polybar/polybar">polybar</a> currently looks:
|
||
<img src="https://sqrtminusone.xyz/images/literate--polybar.png" alt=""></p>
|
||
<p>It has:</p>
|
||
<ul>
|
||
<li>colors from the general color theme;</li>
|
||
<li>powerline-ish decorations between modules.</li>
|
||
</ul>
|
||
<h3 id="colors">Colors</h3>
|
||
<p>The &ldquo;colors&rdquo; part is straightforward enough. Polybar can use <code>Xresources</code>, so we just need to generate the appropriate bindings of Xresources to the polybar variables:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: get-polybar-colors
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :var table=colors :tangle no
|
||
</span></span><span style="display:flex;"><span>(mapconcat
|
||
</span></span><span style="display:flex;"><span> (lambda (elem)
|
||
</span></span><span style="display:flex;"><span> (format &#34;%s = ${xrdb:%s}&#34; (nth 0 elem) (nth 1 elem)))
|
||
</span></span><span style="display:flex;"><span> (seq-filter
|
||
</span></span><span style="display:flex;"><span> (lambda (elem) (when-let (name (nth 1 elem))
|
||
</span></span><span style="display:flex;"><span> (not (string-empty-p name))))
|
||
</span></span><span style="display:flex;"><span> table)
|
||
</span></span><span style="display:flex;"><span> &#34;\n&#34;)
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+begin_src conf-windows :noweb yes
|
||
</span></span><span style="display:flex;"><span>[colors]
|
||
</span></span><span style="display:flex;"><span>&lt;&lt;get-polybar-colors()&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>background = ${xrdb:background}
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><h3 id="module-decorations">Module decorations</h3>
|
||
<p>As for the module decorations though, I find it ironic that with all this fancy rendering around I have to resort to Unicode glyphs.</p>
|
||
<p>Anyhow, the approach is to put a glyph between two blocks like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>block1 block2
|
||
</span></span></code></pre></div><p>And set the foreground and background colors like that:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th></th>
|
||
<th>block1</th>
|
||
<th>glyph</th>
|
||
<th>block2</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>foreground</td>
|
||
<td>F1</td>
|
||
<td>B2</td>
|
||
<td>F2</td>
|
||
</tr>
|
||
<tr>
|
||
<td>background</td>
|
||
<td>B1</td>
|
||
<td>B1</td>
|
||
<td>B2</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>So, that&rsquo;s a start. First, let&rsquo;s define the glyph symbols in the polybar config:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">[glyph]</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">gleft</span> <span style="color:#666">=</span> <span style="color:#ba2121"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">gright</span> <span style="color:#666">=</span> <span style="color:#ba2121"></span>
|
||
</span></span></code></pre></div><h4 id="defining-modules">Defining modules</h4>
|
||
<p>As we want to interweave polybar modules with these glyphs in the right order and with the right colors, it is reasonable to define a single source of truth:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: polybar_modules
|
||
</span></span><span style="display:flex;"><span>| Index | Module | Color | Glyph |
|
||
</span></span><span style="display:flex;"><span>|-------+-------------+---------------+-------|
|
||
</span></span><span style="display:flex;"><span>| 1 | pulseaudio | light-magenta | + |
|
||
</span></span><span style="display:flex;"><span>| 2 | mpd | magenta | + |
|
||
</span></span><span style="display:flex;"><span>| 9 | battery | light-cyan | + |
|
||
</span></span><span style="display:flex;"><span>| 3 | cpu | cyan | + |
|
||
</span></span><span style="display:flex;"><span>| 4 | ram-memory | light-green | + |
|
||
</span></span><span style="display:flex;"><span>| 5 | swap-memory | green | + |
|
||
</span></span><span style="display:flex;"><span>| 6 | network | light-red | + |
|
||
</span></span><span style="display:flex;"><span>| 7 | openvpn | light-red | |
|
||
</span></span><span style="display:flex;"><span>| 8 | xkeyboard | red | + |
|
||
</span></span><span style="display:flex;"><span>| 10 | weather | light-yellow | + |
|
||
</span></span><span style="display:flex;"><span>| 12 | sun | yellow | + |
|
||
</span></span><span style="display:flex;"><span>| 13 | aw-afk | light-blue | + |
|
||
</span></span><span style="display:flex;"><span>| 14 | date | blue | + |
|
||
</span></span></code></pre></div><p>Also excluding some modules from certain monitors, which for now is about excluding <code>battery</code> from the monitors of my desktop PC:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: polybar_modules_exclude
|
||
</span></span><span style="display:flex;"><span>| Monitor | Exclude |
|
||
</span></span><span style="display:flex;"><span>|----------+---------|
|
||
</span></span><span style="display:flex;"><span>| DVI-D-0 | battery |
|
||
</span></span><span style="display:flex;"><span>| HDMI-A-0 | battery |
|
||
</span></span></code></pre></div><h4 id="generating-glyphs">Generating glyphs</h4>
|
||
<p>To generate the required set of glyphs, we need a glyph for every possible combination of adjacent colors that can occur in polybar.</p>
|
||
<p>Most of these combinations can be inferred from the <code>polybar_modules</code> table, the rest are defined in another table:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: polybar_extra_colors
|
||
</span></span><span style="display:flex;"><span>| Color 1 | Color 2 |
|
||
</span></span><span style="display:flex;"><span>|------------+---------------|
|
||
</span></span><span style="display:flex;"><span>| background | white |
|
||
</span></span><span style="display:flex;"><span>| background | light-magenta |
|
||
</span></span><span style="display:flex;"><span>| blue | background |
|
||
</span></span></code></pre></div><p>There&rsquo;s a definition of the source block with the required variables:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: polybar-generate-glyphs
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude extra=polybar_extra_colors
|
||
</span></span><span style="display:flex;"><span>...source...
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>And there is the source block itself (because I want to have some syntax highlighting for this one in the post):</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">let*</span> ((<span style="color:#19177c">monitors</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">thread-last</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exclude-table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-map</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>) (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">el</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-uniq</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">exclude-combinations</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-map</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">monitor</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-map</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>) (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">el</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>) (<span style="color:#008000">and</span> (<span style="color:#00f">string-equal</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">monitor</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">el</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exclude-table</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>(<span style="color:#666">,@</span><span style="color:#19177c">monitors</span> <span style="color:#ba2121">&#34;&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">module-glyph-combinations</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">thread-last</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exclude-combinations</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-map</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">exclude</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">thread-last</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#00f">elt</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#008000">or</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">member</span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#00f">elt</span>) <span style="color:#19177c">exclude</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#00f">string-equal</span> (<span style="color:#00f">nth</span> <span style="color:#666">3</span> <span style="color:#00f">elt</span>) <span style="color:#ba2121">&#34;+&#34;</span>)))))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-uniq</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">color-changes</span> <span style="color:#800">nil</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">e</span> <span style="color:#19177c">extra</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">add-to-list</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;color-changes</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">e</span>) <span style="color:#ba2121">&#34;--&#34;</span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">e</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">comb</span> <span style="color:#19177c">module-glyph-combinations</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dotimes</span> (<span style="color:#19177c">i</span> (<span style="color:#00f">1-</span> (<span style="color:#00f">length</span> <span style="color:#19177c">comb</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">add-to-list</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;color-changes</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span> (<span style="color:#00f">nth</span> <span style="color:#666">2</span> (<span style="color:#00f">nth</span> <span style="color:#19177c">i</span> <span style="color:#19177c">comb</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;--&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">2</span> (<span style="color:#00f">nth</span> (<span style="color:#00f">1+</span> <span style="color:#19177c">i</span>) <span style="color:#19177c">comb</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapconcat</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">colors</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">el</span> <span style="color:#ba2121">&#34;--&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">[module/glyph-%s--%s]
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">type = custom/text
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">content-background = ${colors.%s}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">content-foreground = ${colors.%s}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">content = ${glyph.gright}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">content-font = 5&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">colors</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">colors</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">colors</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">colors</span>))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">color-changes</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;\n&#34;</span>))
|
||
</span></span></code></pre></div><p>Here&rsquo;s a rough outline of how the code works:</p>
|
||
<ul>
|
||
<li><code>monitors</code> is a list of unique monitors in <code>exclude-table</code></li>
|
||
<li><code>exclude-combilnations</code> is a list of lists of module names to be excluded for each monitor</li>
|
||
<li><code>module-glyphs-combinations</code> is a list of lists of actual modules for each monitor</li>
|
||
<li><code>color-changes</code> is a list of unique adjacent colors across modules in all monitors</li>
|
||
</ul>
|
||
<p>Finally, <code>color-changes</code> is used to generate glyph modules that look like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">[module/glyph-light-cyan--cyan]</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">type</span> <span style="color:#666">=</span> <span style="color:#ba2121">custom/text</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">content-background</span> <span style="color:#666">=</span> <span style="color:#ba2121">${colors.light-cyan}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">content-foreground</span> <span style="color:#666">=</span> <span style="color:#ba2121">${colors.cyan}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">content</span> <span style="color:#666">=</span> <span style="color:#ba2121">${glyph.gright}</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#7d9029">content-font</span> <span style="color:#666">=</span> <span style="color:#ba2121">5</span>
|
||
</span></span></code></pre></div><p>As of now, 15 of such modules is generated.</p>
|
||
<p>And including this in the polybar config itself:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src conf-windows :noweb yes
|
||
</span></span><span style="display:flex;"><span>&lt;&lt;polybar-generate-glyphs()&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><h4 id="individual-modules">Individual modules</h4>
|
||
<p>Another thing we need to do is to set the color of modules in accordance with the <code>polybar_modules</code> table. The background can be determined from the <code>Color</code> column with the following code block:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: get-polybar-bg
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :var table=polybar_modules module=&#34;pulseaudio&#34;
|
||
</span></span><span style="display:flex;"><span>(format
|
||
</span></span><span style="display:flex;"><span> &#34;${colors.%s}&#34;
|
||
</span></span><span style="display:flex;"><span> (nth
|
||
</span></span><span style="display:flex;"><span> 2
|
||
</span></span><span style="display:flex;"><span> (seq-find
|
||
</span></span><span style="display:flex;"><span> (lambda (el) (string-equal (nth 1 el) module))
|
||
</span></span><span style="display:flex;"><span> table)))
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>And that block is meant to be invoked in each module definition, e.g. for the <code>cpu</code> module:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src conf-windows :noweb yes
|
||
</span></span><span style="display:flex;"><span>[module/cpu]
|
||
</span></span><span style="display:flex;"><span>type = internal/cpu
|
||
</span></span><span style="display:flex;"><span>format = &#34; &lt;label&gt;&#34;
|
||
</span></span><span style="display:flex;"><span>label = %percentage%%
|
||
</span></span><span style="display:flex;"><span>format-background = &lt;&lt;get-polybar-bg(module=&#34;cpu&#34;)&gt;&gt;
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><h4 id="global-polybar-configuration">Global polybar configuration</h4>
|
||
<p>To configure polybar itself, we first need to generate a set of modules for each monitor.</p>
|
||
<p>Here is the source block definition:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+NAME: polybar-generate-modules
|
||
</span></span><span style="display:flex;"><span>#+begin_src emacs-lisp :var table=polybar_modules exclude-table=polybar_modules_exclude monitor=&#34;DVI-D-0&#34; first-color=&#34;background&#34; last-color=&#34;background&#34;
|
||
</span></span><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>The parameters here, excluding the two required tables, are:</p>
|
||
<ul>
|
||
<li><code>monitor</code> - the current monitor on which to filter out the blocks by the <code>polybar_modules_exclude</code> table,</li>
|
||
<li><code>first-color</code> - the first color of the first glyph,</li>
|
||
<li><code>last-color</code> - the second color of the last glyph.</li>
|
||
</ul>
|
||
<p>And here is the source:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">let*</span> ((<span style="color:#19177c">exclude-modules</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">thread-last</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exclude-table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>) (<span style="color:#00f">string-equal</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">monitor</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-map</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>) (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">el</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">modules</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">thread-last</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">table</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>) (<span style="color:#19177c">not</span> (<span style="color:#00f">member</span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">el</span>) <span style="color:#19177c">exclude-modules</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">prev-color</span> <span style="color:#19177c">first-color</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">ret</span> <span style="color:#800">nil</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapconcat</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">el</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">apply</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#00f">#&#39;concat</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">list</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">string-equal</span> (<span style="color:#00f">nth</span> <span style="color:#666">3</span> <span style="color:#19177c">el</span>) <span style="color:#ba2121">&#34;+&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">ret</span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;glyph-%s--%s &#34;</span> <span style="color:#19177c">prev-color</span> (<span style="color:#00f">nth</span> <span style="color:#666">2</span> <span style="color:#19177c">el</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">prev-color</span> (<span style="color:#00f">nth</span> <span style="color:#666">2</span> <span style="color:#19177c">el</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">ret</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">el</span>))))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">modules</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34; &#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> (<span style="color:#19177c">string-empty-p</span> <span style="color:#19177c">last-color</span>) (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34; glyph-%s--%s &#34;</span> <span style="color:#19177c">prev-color</span> <span style="color:#19177c">last-color</span>))))
|
||
</span></span></code></pre></div><p>Here&rsquo;s how it evaluates on my current monitor:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>glyph-background--light-magenta pulseaudio glyph-light-magenta--magenta mpd glyph-magenta--cyan cpu glyph-cyan--light-green ram-memory glyph-light-green--green swap-memory glyph-green--light-red network openvpn glyph-light-red--red xkeyboard glyph-red--light-yellow weather glyph-light-yellow--yellow sun glyph-yellow--light-blue aw-afk glyph-light-blue--blue date glyph-blue--background
|
||
</span></span></code></pre></div><p>The polybar config doesn&rsquo;t support conditional statements, but it does support environment variables, so we can pass the parameters from something like a bash script. Here&rsquo;s an excerpt from mine:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">declare</span> -A <span style="color:#19177c">BLOCKS</span><span style="color:#666">=(</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">[</span><span style="color:#ba2121">&#34;eDP&#34;</span><span style="color:#666">]=</span><span style="color:#ba2121">&#34;&lt;&lt;polybar-generate-modules(monitor=&#34;</span>eDP<span style="color:#ba2121">&#34;)&gt;&gt;&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">[</span><span style="color:#ba2121">&#34;eDP-1&#34;</span><span style="color:#666">]=</span><span style="color:#ba2121">&#34;&lt;&lt;polybar-generate-modules(monitor=&#34;</span>eDP-1<span style="color:#ba2121">&#34;)&gt;&gt;&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">[</span><span style="color:#ba2121">&#34;DVI-D-0&#34;</span><span style="color:#666">]=</span><span style="color:#ba2121">&#34;&lt;&lt;polybar-generate-modules(monitor=&#34;</span>DVI-D-0<span style="color:#ba2121">&#34;)&gt;&gt;&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">[</span><span style="color:#ba2121">&#34;HDMI-A-0&#34;</span><span style="color:#666">]=</span><span style="color:#ba2121">&#34;&lt;&lt;polybar-generate-modules(monitor=&#34;</span>HDMI-A-0<span style="color:#ba2121">&#34;)&gt;&gt;&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#666">)</span>
|
||
</span></span><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span>pkill polybar
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">for</span> m in <span style="color:#008000;font-weight:bold">$(</span>xrandr --query | grep <span style="color:#ba2121">&#34; connected&#34;</span> | cut -d<span style="color:#ba2121">&#34; &#34;</span> -f1<span style="color:#008000;font-weight:bold">)</span>; <span style="color:#008000;font-weight:bold">do</span>
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">export</span> <span style="color:#19177c">RIGHT_BLOCKS</span><span style="color:#666">=</span><span style="color:#b68;font-weight:bold">${</span><span style="color:#19177c">BLOCKS</span>[<span style="color:#19177c">$MONITOR</span>]<span style="color:#b68;font-weight:bold">}</span>
|
||
</span></span><span style="display:flex;"><span> polybar mybar &amp;
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">done</span>
|
||
</span></span></code></pre></div><p>(The full script has a lot of stuff that is not relevant to this post, but you can <a href="https://github.com/SqrtMinusOne/dotfiles/blob/master/Desktop.org#launch-script-1">check here</a> if you are interested.)</p>
|
||
<p>So, in the case of polybar, literate configuration allows for implementing a sort of logic that wouldn&rsquo;t be available with the base configuration (also a promise of projects like Guix Home, by the way). Maintaining this configuration, e.g. changing the order of modules, is much easier this way than it would be if everything was hardcoded in the polybar config itself.</p>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>Using EXWM and perspective.el on multi-monitor setup</title>
|
||
<link>https://sqrtminusone.xyz/posts/2022-01-03-exwm/</link>
|
||
<pubDate>Mon, 03 Jan 2022 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2022-01-03-exwm/</guid>
|
||
<content type="html">
|
||
<p>I wrote about <a href="https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/">Emacs and i3</a> integration around two months ago. Shortly after however, I decided to give EXWM another try, mainly because my largest reservation - lack of performance - seems to have been resolved by updates to the native compilation since my first attempt. Or I may have lost some sensitivity to that issue. Regardless, the second dive into EXWM thus far feels successful, and I think it&rsquo;s the right time to share some of my thoughts on the subject.</p>
|
||
<p>Before we start though, I&rsquo;ll point out that I won&rsquo;t go into detail about the initial setup. I think David Wilson&rsquo;s &ldquo;<a href="https://systemcrafters.net/emacs-desktop-environment/">Emacs Desktop Environment</a>&rdquo; series describes this part pretty well, so I don&rsquo;t feel the need to repeat much of that.</p>
|
||
<p>This post is a sort of a snapshot of the path from the baseline of <a href="https://github.com/daviwil/emacs-from-scratch/blob/master/Desktop.org">Emacs From Scratch</a> to my image of a perfect window manager, and it may or may not be coincidental that the latter resembles i3 in many aspects.</p>
|
||
<p>After all, I was using i3 for more than two years, so it&rsquo;s not something I can easily let go of. But I think (or would like to think) that&rsquo;s because the ideas are good, not because I&rsquo;m overly conservative in my workflow choices.</p>
|
||
<h2 id="perspective-dot-el">perspective.el</h2>
|
||
<p><a href="https://github.com/nex3/perspective-el">perspective.el</a> is one package I like that provides workspaces for Emacs, called &ldquo;perspectives&rdquo;. Each perspective has a separate buffer list, window layout, and a few other things that make it easier to separate things within Emacs.</p>
|
||
<p>One feature I&rsquo;d like to highlight is integration between perspective.el and <a href="https://github.com/Alexander-Miller/treemacs">treemacs</a>, where one perspective can have a separate treemacs tree. Although now tab-bar.el seems to be getting into shape to compete with perspective.el, as of the time of this writing, there&rsquo;s no such integration, at least not out of the box.</p>
|
||
<p>perspective.el works with EXWM more or less as one would expect - each EXWM workspace has its own set of perspectives. That way it feels somewhat like having multiple Emacs frames in a tiling window manager, although, of course, much more integrated with Emacs.</p>
|
||
<p>However, there are still some issues. For instance, I was having strange behaviors with floating windows, EXWM buffers in perspectives, etc. So I&rsquo;ve made a package called <a href="https://github.com/SqrtMinusOne/perspective-exwm.el">perspective-exwm.el</a> that does two things:</p>
|
||
<ul>
|
||
<li>Fixes issues I found with some advises and hooks. Take a look at the package homepage for more detail on that.</li>
|
||
<li>Provides some additional functionality that makes use of both perspective.el and EXWM.</li>
|
||
</ul>
|
||
<p>So, you can install the package however you normally do so. E.g. I do that with straight.el &amp; use-package:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">perspective-exwm</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>)
|
||
</span></span></code></pre></div><p>Then load the provided minor mode before <code>exwm-init</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">exwm</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">perspective-exwm-mode</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">exwm-init</span>))
|
||
</span></span></code></pre></div><h3 id="initial-perspective-names">Initial perspective names</h3>
|
||
<p>One nice thing this package can do is set up the initial perspective names for different workspaces. By default, enabling <code>perspective-exwm-mode</code> sets names like <code>main-1</code> for workspace with index 1 and so on, because otherwise different perspectives will share the same <code>*scratch*</code> buffer.</p>
|
||
<p>But names can be overridden like that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">perspective-exwm-override-initial-name</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>((<span style="color:#666">0</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;misc&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">1</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;core&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">2</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;browser&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">3</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;comms&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">4</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;dev&#34;</span>)))
|
||
</span></span></code></pre></div><h3 id="assigning-apps-to-workspaces-and-perspectives">Assigning apps to workspaces and perspectives</h3>
|
||
<p>By default, a new Emacs buffer opens in the current perspective in the current workspace, but sure enough, it&rsquo;s possible to change that.</p>
|
||
<p>For EXWM windows, the <code>perspective-exwm</code> package provides a function called <code>perspective-exwm-assign-window</code>, which is intended to be used in <code>exwm-manage-finish-hook</code>, for instance:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-configure-window</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">exwm-class-name</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">or</span> <span style="color:#ba2121">&#34;Firefox&#34;</span> <span style="color:#ba2121">&#34;Nightly&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">perspective-exwm-assign-window</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:workspace-index</span> <span style="color:#666">2</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:persp-name</span> <span style="color:#ba2121">&#34;browser&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;Alacritty&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">perspective-exwm-assign-window</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:persp-name</span> <span style="color:#ba2121">&#34;term&#34;</span>))))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;exwm-manage-finish-hook</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">my/exwm-configure-window</span>)
|
||
</span></span></code></pre></div><p>This hook is run after a new EXWM buffer is created and configured in the context of this buffer, so it seems customary to do such settings there. With this snippet, Firefox will always open in workspace 2 in the perspective named &ldquo;browser&rdquo;, and Alacritty will always open in the current workspace in the perspective named &ldquo;term&rdquo;.</p>
|
||
<p>To pull this off for various Emacs apps, it is necessary to open the right EXWM workspace and perspective before opening the app. As I use <a href="https://github.com/noctuid/general.el">general.el</a>, I made a macro to automate that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defmacro</span> <span style="color:#19177c">my/command-in-persp</span> (<span style="color:#19177c">command-name</span> <span style="color:#19177c">persp-name</span> <span style="color:#19177c">workspace-index</span> <span style="color:#008000">&amp;rest</span> <span style="color:#19177c">args</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`&#39;</span>((<span style="color:#008000">lambda</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#008000">and</span> <span style="color:#666">,</span><span style="color:#19177c">workspace-index</span> (<span style="color:#00f">fboundp</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">exwm-workspace-switch-create</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">exwm-workspace-switch-create</span> <span style="color:#666">,</span><span style="color:#19177c">workspace-index</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">persp-switch</span> <span style="color:#666">,</span><span style="color:#19177c">persp-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">delete-other-windows</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">,@</span><span style="color:#19177c">args</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:wk</span> <span style="color:#666">,</span><span style="color:#19177c">command-name</span>))
|
||
</span></span></code></pre></div><p><code>fboundp</code> is meant to provide compatibility with running Emacs without EXWM. Usage of the macro is as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">my-leader-def</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:infix</span> <span style="color:#ba2121">&#34;as&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;&#34;</span> <span style="color:#666">&#39;</span>(<span style="color:#008000">:which-key</span> <span style="color:#ba2121">&#34;emms&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;s&#34;</span> (<span style="color:#19177c">my/command-in-persp</span> <span style="color:#ba2121">&#34;emms&#34;</span> <span style="color:#ba2121">&#34;EMMS&#34;</span> <span style="color:#666">0</span> (<span style="color:#19177c">emms-smart-browse</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>)
|
||
</span></span></code></pre></div><p><code>my-leader-def</code> is a <a href="https://github.com/noctuid/general.el#creating-new-key-definers">custom definer</a>. That way the defined keybinding opens <a href="https://www.gnu.org/software/emms/">EMMS</a> in the workspace 0 in the perspective &ldquo;EMMS&rdquo;. I have this for several other apps, like elfeed, notmuch, dired <code>$HOME</code> and so on.</p>
|
||
<h3 id="some-workflow-notes">Some workflow notes</h3>
|
||
<p>As I said above, using perspectives in EXWM makes a lot of sense. Because all the EXWM workspace share the same buffer list (sans X windows), and because Emacs becomes the central program (for instance, it can&rsquo;t be easily closed), it is only natural to split the buffer list.</p>
|
||
<p>Another aspect of using EXWM is that it becomes very easy to work with code on multiple monitors. While it may signify issues with the code in question if such need arises, having that possibility is still handy and it&rsquo;s not something easily replicable on other tiling WMs. <code>perspective-exwm</code> also presents some features here, for instance, <code>M-x perspective-exwm-copy-to-workspace</code> can be used to copy the current perspective to the adjacent monitor.</p>
|
||
<p>Also, in my opinion, Emacs apps like <a href="https://www.gnu.org/software/emms/">EMMS</a> and <a href="https://github.com/skeeto/elfeed">elfeed</a> deserve to be on the same &ldquo;level&rdquo; as &ldquo;proper&rdquo; apps like a browser. On other tiling WMs, something like that can be done with Emacs daemon and multiple Emacs frames, but with EXWM and perspectives this seems natural without much extra work.</p>
|
||
<p>As for switching between X windows and perspectives, I ended up preferring to have one perspective for all X windows in the workspace, at least if these windows are full-fledged apps. For instance, all my messengers go to the workspace 3 to the perspective &ldquo;comms&rdquo;, and I switch between them with <code>M-x perspective-exwm-cycle-exwm-buffers-&lt;forward|backward&gt;</code>, bound to <code>s-[</code> and <code>s-]</code>. For switching perspectives, I&rsquo;ve bound <code>s-,</code> and <code>s-.</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-input-global-keys</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Switch perspectives</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-,&#34;</span>) <span style="color:#666">.</span> <span style="color:#19177c">persp-prev</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-.&#34;</span>) <span style="color:#666">.</span> <span style="color:#19177c">persp-next</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; EXWM buffers</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-[&#34;</span>) <span style="color:#666">.</span> <span style="color:#19177c">perspective-exwm-cycle-exwm-buffers-backward</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-]&#34;</span>) <span style="color:#666">.</span> <span style="color:#19177c">perspective-exwm-cycle-exwm-buffers-forward</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>)
|
||
</span></span></code></pre></div><h2 id="workspaces-on-multiple-monitors">Workspaces on multiple monitors</h2>
|
||
<p>Here, <code>exwm-randr</code> provides basic functionality for running EXWM on multiple monitors. For instance, with configuration like that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">require</span> <span style="color:#19177c">&#39;exwm-randr</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">exwm-randr-enable</span>)
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">;; The script is generated by ARandR</span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">start-process-shell-command</span> <span style="color:#ba2121">&#34;xrandr&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;~/bin/scripts/screen-layout&#34;</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">when</span> (<span style="color:#19177c">string=</span> (<span style="color:#00f">system-name</span>) <span style="color:#ba2121">&#34;indigo&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-randr-workspace-monitor-plist</span> <span style="color:#666">&#39;</span>(<span style="color:#666">2</span> <span style="color:#ba2121">&#34;DVI-D-0&#34;</span> <span style="color:#666">3</span> <span style="color:#ba2121">&#34;DVI-D-0&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#666">...</span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">exwm-init</span>)
|
||
</span></span></code></pre></div><p>workspaces 2 and 3 on the machine with hostname &ldquo;indigo&rdquo; will be displayed on the monitor <code>DVI-D-0</code>.</p>
|
||
<p>However, some features, common in other tiling WMs, are missing in EXWM out of the box, namely:</p>
|
||
<ul>
|
||
<li>a command to <a href="https://i3wm.org/docs/userguide.html#_focusing_moving_containers">switch to another monitor</a>;</li>
|
||
<li>a command to <a href="https://i3wm.org/docs/userguide.html#move_to_outputs">move the current workspace to another monitor</a>;</li>
|
||
<li>using the same commands to switch between windows and monitors.</li>
|
||
</ul>
|
||
<p>Here&rsquo;s my take on implementing them.</p>
|
||
<h3 id="tracking-recently-used-workspaces">Tracking recently used workspaces</h3>
|
||
<p>First up though, we need to track the workspaces in the usage order. I&rsquo;m not sure if there&rsquo;s some built-in functionality in EXWM for that, but it seems simple enough to implement.</p>
|
||
<p>Here is a snippet of code that does it:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/exwm-last-workspaces</span> <span style="color:#666">&#39;</span>(<span style="color:#666">1</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-store-last-workspace</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Save the last workspace to </span><span style="color:#19177c">`my/exwm-last-workspaces&#39;</span><span style="color:#ba2121">.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">my/exwm-last-workspaces</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-uniq</span> (<span style="color:#00f">cons</span> <span style="color:#19177c">exwm-workspace-current-index</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">my/exwm-last-workspaces</span>))))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;exwm-workspace-switch-hook</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">my/exwm-store-last-workspace</span>)
|
||
</span></span></code></pre></div><p>The variable <code>my/exwm-last-workspaces</code> stores the workspace indices; the first item is the index of the current workspace, the second item is the index of the previous workspace, and so on.</p>
|
||
<p>One note here is that workspaces may also disappear (e.g. after <code>M-x exwm-workspace-delete</code>), so we also need a function to clean the list:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-last-workspaces-clear</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Clean </span><span style="color:#19177c">`my/exwm-last-workspaces&#39;</span><span style="color:#ba2121"> from deleted workspaces.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">my/exwm-last-workspaces</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">i</span>) (<span style="color:#00f">nth</span> <span style="color:#19177c">i</span> <span style="color:#19177c">exwm-workspace--list</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">my/exwm-last-workspaces</span>)))
|
||
</span></span></code></pre></div><h3 id="the-monitor-list">The monitor list</h3>
|
||
<p>The second piece of the puzzle is getting the monitor list in the right order.</p>
|
||
<p>While it is possible to retrieve the monitor list from <code>exwm-randr-workspace-output-plist</code>, this won&rsquo;t scale well beyond two monitors, mainly because changing this variable may screw up the order.</p>
|
||
<p>So the easiest way is to just define the variable like that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/exwm-monitor-list</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> (<span style="color:#00f">system-name</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;indigo&#34;</span> <span style="color:#666">&#39;</span>(<span style="color:#800">nil</span> <span style="color:#ba2121">&#34;DVI-D-0&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">_</span> <span style="color:#666">&#39;</span>(<span style="color:#800">nil</span>))))
|
||
</span></span></code></pre></div><p>If you are changing the RandR configuration on the fly, this variable will also need to be changed, but for now, I don&rsquo;t have such a necessity.</p>
|
||
<p>A function to get the current monitor:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-get-current-monitor</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Return the current monitor name or nil.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">plist-get</span> <span style="color:#19177c">exwm-randr-workspace-output-plist</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-position</span> (<span style="color:#00f">selected-frame</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exwm-workspace--list</span>)))
|
||
</span></span></code></pre></div><p>And a function to cycle the monitor list in either direction:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-get-other-monitor</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Cycle the monitor list in the direction DIR.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">DIR is either &#39;left or &#39;right.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">nth</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">%</span> (<span style="color:#00f">+</span> (<span style="color:#19177c">cl-position</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/exwm-get-current-monitor</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">my/exwm-monitor-list</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:test</span> <span style="color:#00f">#&#39;string-equal</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">length</span> <span style="color:#19177c">my/exwm-monitor-list</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;right</span> <span style="color:#666">1</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;left</span> <span style="color:#666">-1</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">length</span> <span style="color:#19177c">my/exwm-monitor-list</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">my/exwm-monitor-list</span>))
|
||
</span></span></code></pre></div><h3 id="switch-to-another-monitor">Switch to another monitor</h3>
|
||
<p>With the functions from the previous two sections, we can implement switching to another monitor by switching to the most recently used workspace on that monitor.</p>
|
||
<video controls width="100%">
|
||
<source src="https://sqrtminusone.xyz/videos/exwm-workspace-switch.mp4" type="video/mp4">
|
||
</video>
|
||
<p>One caveat here is that on the startup the <code>my/exwm-last-workspaces</code> variable won&rsquo;t have any values from other monitor(s), so this list is concatenated with the list of available workspace indices.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-switch-to-other-monitor</span> (<span style="color:#008000">&amp;optional</span> <span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Switch to another monitor.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/exwm-last-workspaces-clear</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">exwm-workspace-switch</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">with</span> <span style="color:#19177c">other-monitor</span> <span style="color:#00f">=</span> (<span style="color:#19177c">my/exwm-get-other-monitor</span> (<span style="color:#008000">or</span> <span style="color:#19177c">dir</span> <span style="color:#19177c">&#39;right</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">for</span> <span style="color:#19177c">i</span> <span style="color:#19177c">in</span> (<span style="color:#00f">append</span> <span style="color:#19177c">my/exwm-last-workspaces</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> <span style="color:#19177c">i</span> <span style="color:#19177c">from</span> <span style="color:#666">0</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">for</span> <span style="color:#19177c">_</span> <span style="color:#19177c">in</span> <span style="color:#19177c">exwm-workspace--list</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">collect</span> <span style="color:#19177c">i</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">if</span> (<span style="color:#008000">if</span> <span style="color:#19177c">other-monitor</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">string-equal</span> (<span style="color:#00f">plist-get</span> <span style="color:#19177c">exwm-randr-workspace-output-plist</span> <span style="color:#19177c">i</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">other-monitor</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#00f">plist-get</span> <span style="color:#19177c">exwm-randr-workspace-output-plist</span> <span style="color:#19177c">i</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">return</span> <span style="color:#19177c">i</span>)))
|
||
</span></span></code></pre></div><p>I bind this function to <code>s-q</code>, as I&rsquo;m used from i3.</p>
|
||
<h3 id="move-the-workspace-to-another-monitor">Move the workspace to another monitor</h3>
|
||
<p>Now, moving the workspace to another monitor.</p>
|
||
<video controls width="100%">
|
||
<source src="https://sqrtminusone.xyz/videos/exwm-workspace-move.mp4" type="video/mp4">
|
||
</video>
|
||
<p>This is actually quite easy to pull off - one just has to update <code>exwm-randr-workspace-monitor-plist</code> accordingly and run <code>exwm-randr-refresh</code>. I just add another check there because I don&rsquo;t want some monitor to remain without workspaces at all.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-workspace-switch-monitor</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Move the current workspace to another monitor.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">new-monitor</span> (<span style="color:#19177c">my/exwm-get-other-monitor</span> <span style="color:#19177c">&#39;right</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">current-monitor</span> (<span style="color:#19177c">my/exwm-get-current-monitor</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#008000">and</span> <span style="color:#19177c">current-monitor</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">&gt;=</span> <span style="color:#666">1</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> (<span style="color:#19177c">key</span> <span style="color:#19177c">value</span>) <span style="color:#19177c">on</span> <span style="color:#19177c">exwm-randr-workspace-monitor-plist</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">by</span> <span style="color:#19177c">&#39;cddr</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">if</span> (<span style="color:#00f">string-equal</span> <span style="color:#19177c">value</span> <span style="color:#19177c">current-monitor</span>) <span style="color:#19177c">sum</span> <span style="color:#666">1</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">error</span> <span style="color:#ba2121">&#34;Can&#39;t remove the last workspace on the monitor!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-randr-workspace-monitor-plist</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">map-delete</span> <span style="color:#19177c">exwm-randr-workspace-monitor-plist</span> <span style="color:#19177c">exwm-workspace-current-index</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> <span style="color:#19177c">new-monitor</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-randr-workspace-monitor-plist</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">plist-put</span> <span style="color:#19177c">exwm-randr-workspace-monitor-plist</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exwm-workspace-current-index</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">new-monitor</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">exwm-randr-refresh</span>))
|
||
</span></span></code></pre></div><p>In my configuration this is bound to <code>s-&lt;tab&gt;</code>.</p>
|
||
<h3 id="windmove-between-monitors">Windmove between monitors</h3>
|
||
<p>And the final (for now) piece of the puzzle is using the same command to switch between windows and monitors. E.g. when the focus is on the right-most window on one monitor, I want the command to switch to the left-most window on the monitor to the right instead of saying &ldquo;No window right from the selected window&rdquo;, as <code>windmove-right</code> does.</p>
|
||
<p>So here is my implementation of that. It always does <code>windmove-do-select-window</code> for <code>'down</code> and <code>'up</code>. For <code>'right</code> and <code>'left</code> though, the function calls the previously defined function to switch to other monitor if <code>windmove-find-other-window</code> doesn&rsquo;t return anything.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-windmove</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Move to window or monitor in the direction DIR.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#008000">or</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">dir</span> <span style="color:#19177c">&#39;down</span>) (<span style="color:#00f">eq</span> <span style="color:#19177c">dir</span> <span style="color:#19177c">&#39;up</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">windmove-do-window-select</span> <span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">other-window</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">dir</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-monitor</span> (<span style="color:#19177c">my/exwm-get-other-monitor</span> <span style="color:#19177c">dir</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">opposite-dir</span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;left</span> <span style="color:#19177c">&#39;right</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;right</span> <span style="color:#19177c">&#39;left</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> <span style="color:#19177c">other-window</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">windmove-do-window-select</span> <span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/exwm-switch-to-other-monitor</span> <span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#008000">while</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">opposite-dir</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#19177c">windmove-do-window-select</span> <span style="color:#19177c">opposite-dir</span>))))))
|
||
</span></span></code></pre></div><p>I bind it to the corresponding keys like that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-input-global-keys</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Switch windows</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-&lt;left&gt;&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;left</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-&lt;right&gt;&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;right</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-&lt;up&gt;&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;up</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-&lt;down&gt;&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;down</span>)))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-h&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;left</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-l&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;right</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-k&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;up</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-j&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-windmove</span> <span style="color:#19177c">&#39;down</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>)
|
||
</span></span></code></pre></div><h2 id="managing-windows">Managing windows</h2>
|
||
<p>Another thing I want to tackle here is managing windows.</p>
|
||
<p>This section of the post depends on <a href="https://github.com/emacs-evil/evil">evil-mode</a>, which provides a reasonable set of vim-like commands to manage windows. But a few points to improve upon remain.</p>
|
||
<h3 id="moving-windows">Moving windows</h3>
|
||
<p>As I wrote in my <a href="https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/">Emacs and i3</a> post, I want to have a rather specific behavior when moving windows (which does resemble i3 in some way):</p>
|
||
<ul>
|
||
<li>if there is space in the required direction, move the Emacs window there;</li>
|
||
<li>if there is no space in the required direction, but space in two orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;</li>
|
||
</ul>
|
||
<p>I can&rsquo;t say it&rsquo;s better or worse than the built-in functionality or one provided by evil, but I&rsquo;m used to it and I think it fits better for managing a lot of windows.</p>
|
||
<p>So, first, we need a predicate that checks whether there is space in the given direction:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-direction-exists-p</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Check if there is space in the direction DIR.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">Does not take the minibuffer into account.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-some</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">win</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">dir</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">and</span> <span style="color:#19177c">win</span> (<span style="color:#19177c">not</span> (<span style="color:#00f">window-minibuffer-p</span> <span style="color:#19177c">win</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;width</span> <span style="color:#666">&#39;</span>(<span style="color:#19177c">left</span> <span style="color:#19177c">right</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;height</span> <span style="color:#666">&#39;</span>(<span style="color:#19177c">up</span> <span style="color:#19177c">down</span>)))))
|
||
</span></span></code></pre></div><p>And a function to implement that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-move-window</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Move the current window in the direction DIR.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">other-window</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">dir</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-direction</span> (<span style="color:#19177c">my/exwm-direction-exists-p</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;up</span> <span style="color:#19177c">&#39;width</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;down</span> <span style="color:#19177c">&#39;width</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;left</span> <span style="color:#19177c">&#39;height</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;right</span> <span style="color:#19177c">&#39;height</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">and</span> <span style="color:#19177c">other-window</span> (<span style="color:#19177c">not</span> (<span style="color:#00f">window-minibuffer-p</span> <span style="color:#19177c">other-window</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">window-swap-states</span> (<span style="color:#00f">selected-window</span>) <span style="color:#19177c">other-window</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-direction</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">evil-move-window</span> <span style="color:#19177c">dir</span>)))))
|
||
</span></span></code></pre></div><p>My preferred keybindings for this part are, of course, <code>s-&lt;H|J|K|L&gt;</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-input-global-keys</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Moving windows</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-H&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-move-window</span> <span style="color:#19177c">&#39;left</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-L&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-move-window</span> <span style="color:#19177c">&#39;right</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-K&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-move-window</span> <span style="color:#19177c">&#39;up</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#666">,</span>(<span style="color:#19177c">kbd</span> <span style="color:#ba2121">&#34;s-J&#34;</span>) <span style="color:#666">.</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-move-window</span> <span style="color:#19177c">&#39;down</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">...</span>))
|
||
</span></span></code></pre></div><h3 id="resizing-windows">Resizing windows</h3>
|
||
<p>I find this odd that there are different commands to resize tiling and floating windows.</p>
|
||
<video controls width="100%">
|
||
<source src="https://sqrtminusone.xyz/videos/exwm-resize-hydra.mp4" type="video/mp4">
|
||
</video>
|
||
<p>So let&rsquo;s define one command to perform both resizes depending on the context:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/exwm-resize-value</span> <span style="color:#666">5</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-resize-window</span> (<span style="color:#19177c">dir</span> <span style="color:#19177c">kind</span> <span style="color:#008000">&amp;optional</span> <span style="color:#19177c">value</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Resize the current window in the direction DIR.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">DIR is either &#39;height or &#39;width, KIND is either &#39;shrink or
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121"> &#39;grow. VALUE is </span><span style="color:#19177c">`my/exwm-resize-value&#39;</span><span style="color:#ba2121"> by default.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">If the window is an EXWM floating window, execute the
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">corresponding command from the exwm-layout group, execute the
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">command from the evil-window group.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">value</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">value</span> <span style="color:#19177c">my/exwm-resize-value</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">is-exwm-floating</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">and</span> (<span style="color:#19177c">derived-mode-p</span> <span style="color:#19177c">&#39;exwm-mode</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">exwm--floating-frame</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">func</span> (<span style="color:#008000">if</span> <span style="color:#19177c">is-exwm-floating</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">intern</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;exwm-layout-&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">kind</span> (<span style="color:#19177c">&#39;shrink</span> <span style="color:#ba2121">&#34;shrink&#34;</span>) (<span style="color:#19177c">&#39;grow</span> <span style="color:#ba2121">&#34;enlarge&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;-window&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span> (<span style="color:#19177c">&#39;height</span> <span style="color:#ba2121">&#34;&#34;</span>) (<span style="color:#19177c">&#39;width</span> <span style="color:#ba2121">&#34;-horizontally&#34;</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">intern</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;evil-window&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">kind</span> (<span style="color:#19177c">&#39;shrink</span> <span style="color:#ba2121">&#34;-decrease-&#34;</span>) (<span style="color:#19177c">&#39;grow</span> <span style="color:#ba2121">&#34;-increase-&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">symbol-name</span> <span style="color:#19177c">dir</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> <span style="color:#19177c">is-exwm-floating</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">value</span> (<span style="color:#00f">*</span> <span style="color:#666">5</span> <span style="color:#19177c">value</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">funcall</span> <span style="color:#19177c">func</span> <span style="color:#19177c">value</span>)))
|
||
</span></span></code></pre></div><p>This function will call <code>exwm-layout-&lt;shrink|grow&gt;[-horizontally]</code> for EXWM floating window and <code>evil-window-&lt;decrease|increase&gt;-&lt;width|height&gt;</code> otherwise.</p>
|
||
<p>This function can be bound to the required keybindings directly, but I prefer a hydra to emulate the i3 submode:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">defhydra</span> <span style="color:#19177c">my/exwm-resize-hydra</span> (<span style="color:#008000">:color</span> <span style="color:#19177c">pink</span> <span style="color:#008000">:hint</span> <span style="color:#800">nil</span> <span style="color:#008000">:foreign-keys</span> <span style="color:#19177c">run</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">^Resize^
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">_l_: Increase width _h_: Decrease width _j_: Increase height _k_: Decrease height
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">_=_: Balance &#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;h&#34;</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-resize-window</span> <span style="color:#19177c">&#39;width</span> <span style="color:#19177c">&#39;shrink</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;j&#34;</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-resize-window</span> <span style="color:#19177c">&#39;height</span> <span style="color:#19177c">&#39;grow</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;k&#34;</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-resize-window</span> <span style="color:#19177c">&#39;height</span> <span style="color:#19177c">&#39;shrink</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;l&#34;</span> (<span style="color:#008000">lambda</span> () (<span style="color:#008000">interactive</span>) (<span style="color:#19177c">my/exwm-resize-window</span> <span style="color:#19177c">&#39;width</span> <span style="color:#19177c">&#39;grow</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;=&#34;</span> <span style="color:#19177c">balance-windows</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;q&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;quit&#34;</span> <span style="color:#008000">:color</span> <span style="color:#19177c">blue</span>))
|
||
</span></span></code></pre></div><h3 id="splitting-windows">Splitting windows</h3>
|
||
<p><code>M-x evil-window-[v]split</code> (bound to <code>C-w v</code> and <code>C-w s</code> by default) are the default evil command to do splits.</p>
|
||
<p>One EXWM-related issue though is that by default doing such a split &ldquo;copies&rdquo; the current buffer to the new window. But as EXWM buffer cannot be &ldquo;copied&rdquo; like that, some other buffer is displayed in the split, and generally, that&rsquo;s not a buffer I want.</p>
|
||
<p>For instance, I prefer to have Chrome DevTools as a separate window. When I click &ldquo;Inspect&rdquo; on something, the DevTools window replaces my Ungoogled Chromium window. I press <code>C-w v</code>, and most often I have something like <code>*scratch*</code> buffer in the opened split instead of the previous Chromium window.</p>
|
||
<p>To implement better behavior, I define the following advice:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/exwm-fill-other-window</span> (<span style="color:#008000">&amp;rest</span> <span style="color:#19177c">_</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Open the most recently used buffer in the next window.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#008000">and</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">major-mode</span> <span style="color:#19177c">&#39;exwm-mode</span>) (<span style="color:#19177c">not</span> (<span style="color:#00f">eq</span> (<span style="color:#00f">next-window</span>) (<span style="color:#00f">get-buffer-window</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">other-exwm-buffer</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">with</span> <span style="color:#00f">other-buffer</span> <span style="color:#00f">=</span> (<span style="color:#19177c">persp-other-buffer</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">for</span> <span style="color:#19177c">buf</span> <span style="color:#19177c">in</span> (<span style="color:#00f">sort</span> (<span style="color:#19177c">persp-current-buffers</span>) (<span style="color:#008000">lambda</span> (<span style="color:#19177c">a</span> <span style="color:#19177c">_</span>) (<span style="color:#00f">eq</span> <span style="color:#19177c">a</span> <span style="color:#00f">other-buffer</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">with</span> <span style="color:#00f">current-buffer</span> <span style="color:#00f">=</span> (<span style="color:#00f">current-buffer</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">when</span> (<span style="color:#008000">and</span> (<span style="color:#19177c">not</span> (<span style="color:#00f">eq</span> <span style="color:#00f">current-buffer</span> <span style="color:#19177c">buf</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">buffer-live-p</span> <span style="color:#19177c">buf</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#19177c">string-match-p</span> (<span style="color:#19177c">persp--make-ignore-buffer-rx</span>) (<span style="color:#00f">buffer-name</span> <span style="color:#19177c">buf</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#00f">get-buffer-window</span> <span style="color:#19177c">buf</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">return</span> <span style="color:#19177c">buf</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> <span style="color:#19177c">other-exwm-buffer</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">with-selected-window</span> (<span style="color:#00f">next-window</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">switch-to-buffer</span> <span style="color:#19177c">other-exwm-buffer</span>))))))
|
||
</span></span></code></pre></div><p>This is meant to be called after doing an either vertical or horizontal split, so it&rsquo;s advised like that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">advice-add</span> <span style="color:#19177c">&#39;evil-window-split</span> <span style="color:#008000">:after</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">my/exwm-fill-other-window</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">advice-add</span> <span style="color:#19177c">&#39;evil-window-vsplit</span> <span style="color:#008000">:after</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">my/exwm-fill-other-window</span>)
|
||
</span></span></code></pre></div><p>This works as follows. If the current buffer is an EXWM buffer and there are other windows open (that is, <code>(next-window)</code> is not the current window), the function tries to find another suitable buffer to be opened in the split. And that also takes the perspectives into account, so buffers are searched only within the current perspective, and the buffer returned by <code>persp-other-buffer</code> will be the top candidate.</p>
|
||
<h2 id="notes-on-floating-windows">Notes on floating windows</h2>
|
||
<p>Floating windows are not the most stable feature of EXWM.</p>
|
||
<p>One story is that closing a floating window often screws up the current perspective, but that&rsquo;s advised away by my <code>perspective-exwm-mode</code>.</p>
|
||
<p>Another is that these three settings (which are reasonably <a href="https://github.com/daviwil/emacs-from-scratch/blob/5ebd390119a48cac6258843c7d5e570f4591fdd4/show-notes/Emacs-Desktop-04.org#mouse-warping">recommended</a> in the Emacs Desktop series) seem to increase chances of breaking the current EXWM session:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">exwm-workspace-warp-cursor</span> <span style="color:#800">t</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">mouse-autoselect-window</span> <span style="color:#800">t</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">focus-follows-mouse</span> <span style="color:#800">t</span>)
|
||
</span></span></code></pre></div><p>Occasionally they create a loop of mouse warps and focus changes. I found that disabling them just for the floating windows greatly stabilized that part:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/fix-exwm-floating-windows</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">exwm-workspace-warp-cursor</span> <span style="color:#800">nil</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">mouse-autoselect-window</span> <span style="color:#800">nil</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq-local</span> <span style="color:#19177c">focus-follows-mouse</span> <span style="color:#800">nil</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;exwm-floating-setup-hook</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">my/fix-exwm-floating-windows</span>)
|
||
</span></span></code></pre></div><p>However, one particularly unfriendly app is the <a href="https://zoom.us/">Zoom app</a>, which proudly creates a million various popups and still manages to break the EXWM sesssion. Fortunately, it can be used from a browser, which is what I advise to do.</p>
|
||
<h2 id="what-else-not-to-do">What else not to do</h2>
|
||
<p>A couple of final notes to make using EXWM a somewhat better experience.</p>
|
||
<p>First, <a href="https://github.com/daviwil/exwm/commit/7b1be884124711af0a02eac740bdb69446bc54cc">this fix</a> by David helped with <a href="https://github.com/ch11ng/exwm/issues/842">one case</a> of EXWM freezing, which I managed to get into a few times.</p>
|
||
<p>Second, do not run transients while there&rsquo;s an active EXWM window in the workspace, especially if it&rsquo;s it <code>char-mode</code>. That seems to break the session quite securely.</p>
|
||
<p>Third, running <code>shutdown</code> or something like that in the console is not the greatest idea, because things like <code>kill-emacs-hook</code> are not triggered in this case. For instance, EMMS history &amp; elfeed databases are not saved.</p>
|
||
<h2 id="p-dot-s-dot">P.S.</h2>
|
||
<p>The way how characters aligned in my keybinding for EMMS is coincidental and does not carry any semantic value. The <code>a</code> is for &ldquo;app&rdquo;, <code>s</code> is because <code>e</code> and <code>m</code> were already taken by elfeed and notmuch, and the second <code>s</code> is because it&rsquo;s faster to press the same character twice.</p>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>Getting a consistent set of keybindings between i3 and Emacs</title>
|
||
<link>https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/</link>
|
||
<pubDate>Wed, 06 Oct 2021 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2021-10-04-emacs-i3/</guid>
|
||
<content type="html">
|
||
<h2 id="intro">Intro</h2>
|
||
<p>One advantage of EXWM for an Emacs user is that EXWM gives one set of keybindings to manage both Emacs windows and X windows. In every other WM, like my preferred <a href="https://i3wm.org">i3wm</a>, two orthogonal keymaps seem to be necessary. But, as both programs are quite customizable, I want to see whether I can replicate at least some part of the EXWM goodness in i3.</p>
|
||
<p>But why not just use EXWM? One key reason is that to my taste (and perhaps on my hardware) EXWM didn&rsquo;t feel snappy enough. Also, I really like i3&rsquo;s tree-based layout structure; I feel like it fits my workflow much better than anything else I tried, including the master/stack paradigm of <a href="https://xmonad.org/">XMonad</a>, for instance.</p>
|
||
<p>One common point of criticism of i3 is that it is not extensible enough, especially compared to WMs that are configured in an actual programing language, like the mentioned XMonad, <a href="http://www.qtile.org/">Qtile</a>, <a href="https://awesomewm.org/">Awesome</a>, etc. But I think i3&rsquo;s extensibility is underappreciated, although the contents of this article may lie closer to the limits of how far one can go there.</p>
|
||
<p>Here is a small demo of how it currently works:</p>
|
||
<video controls width="100%">
|
||
<source src="https://sqrtminusone.xyz/static/videos/i3-emacs-demo.mp4" type="video/mp4">
|
||
</video>
|
||
<h2 id="emacs-integration">Emacs integration</h2>
|
||
<p>What I&rsquo;m trying to do is actually quite simple, so I&rsquo;m somewhat surprised I didn&rsquo;t find anything similar on the Internet. But I didn&rsquo;t look too hard.</p>
|
||
<p>The basic idea is to launch a normal i3 command with <code>i3-msg</code> in case the current window is not Emacs, otherwise pass that command to Emacs with <code>emacsclient</code>. In Emacs, execute the command if possible, otherwise pass the command back to i3.</p>
|
||
<p>This may seem like a lot of overhead, but I didn&rsquo;t feel it even in the worst case (i3 -&gt; Emacs -&gt; i3), so at least in that regard, the interaction feels seamless. The only concern is that this command flow is vulnerable to Emacs getting stuck, but it is still much less of a problem than with EXWM.</p>
|
||
<p>One interesting observation here is that Emacs windows and X windows are sort of one-level entities, so I can talk just about &ldquo;windows&rdquo;.</p>
|
||
<p>At any rate, we need a script to do the i3 -&gt; Emacs part:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">if</span> <span style="color:#666">[[</span> <span style="color:#008000;font-weight:bold">$(</span>xdotool getactivewindow getwindowname<span style="color:#008000;font-weight:bold">)</span> <span style="color:#666">=</span>~ ^emacs<span style="color:#666">(</span>:.*<span style="color:#666">)</span>?@.* <span style="color:#666">]]</span>; <span style="color:#008000;font-weight:bold">then</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">command</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;(my/emacs-i3-integration \&#34;</span><span style="color:#19177c">$@</span><span style="color:#ba2121">\&#34;)&#34;</span>
|
||
</span></span><span style="display:flex;"><span> emacsclient -e <span style="color:#ba2121">&#34;</span><span style="color:#19177c">$command</span><span style="color:#ba2121">&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">else</span>
|
||
</span></span><span style="display:flex;"><span> i3-msg <span style="color:#19177c">$@</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">fi</span>
|
||
</span></span></code></pre></div><p>My <a href="https://sqrtminusone.xyz/configs/emacs/#custom-frame-title">Emacs frame title is set</a> to <code>emacs[:&lt;projectile-project-name&gt;]@&lt;hostname&gt;</code>, hence the regex. The script is saved to an executable called <code>emacs-i3-integration</code>.</p>
|
||
<p>For this to work, we need to make sure that Emacs starts a server, so here is an expression to do just that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;after-init-hook</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">server-start</span>)
|
||
</span></span></code></pre></div><p>The function <code>my/emacs-i3-integration</code>, which is an entrypoint for the i3 integration, will be defined a bit later.</p>
|
||
<p>And here is a simple macro to do the Emacs -&gt; i3 part:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defmacro</span> <span style="color:#19177c">i3-msg</span> (<span style="color:#008000">&amp;rest</span> <span style="color:#19177c">args</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>(<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;emacs-i3-windmove&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;i3-msg&#34;</span> <span style="color:#666">,@</span><span style="color:#19177c">args</span>))
|
||
</span></span></code></pre></div><h2 id="handling-i3-commands">Handling i3 commands</h2>
|
||
<p>Now we have to handle the required set of i3 commands. It is worth noting here that I&rsquo;m not trying to implement a general mechanism to apply i3 commands to Emacs, rather I&rsquo;m implementing a small subset that I use in my i3 configuration and that maps reasonably to the Emacs concepts.</p>
|
||
<p>Also, I use <a href="https://github.com/emacs-evil/evil">evil-mode</a> and generally configure the software to have vim-style bindings where possible. So if you don&rsquo;t use evil-mode you&rsquo;d have to detangle the given functions from evil, but then, I guess, you do not use super+hjkl to manage windows either.</p>
|
||
<h3 id="focus"><code>focus</code></h3>
|
||
<p>First, for the <code>focus</code> command I want to move to an Emacs window in the given direction if there is one, otherwise move to an X window in the same direction. Fortunately, i3 and <code>windmove</code> have the same names for directions, so the function is rather straightforward.</p>
|
||
<p>One caveat here is that the minibuffer is always the bottom-most Emacs window, so it is necessary to check for that as well.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/emacs-i3-windmove</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">other-window</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">dir</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#008000">or</span> (<span style="color:#00f">null</span> <span style="color:#19177c">other-window</span>) (<span style="color:#00f">window-minibuffer-p</span> <span style="color:#19177c">other-window</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">i3-msg</span> <span style="color:#ba2121">&#34;focus&#34;</span> (<span style="color:#00f">symbol-name</span> <span style="color:#19177c">dir</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">windmove-do-window-select</span> <span style="color:#19177c">dir</span>))))
|
||
</span></span></code></pre></div><p>The relevant section of the i3 config looks like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+h <span style="color:#008000">exec</span> emacs-i3-integration focus left
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+j <span style="color:#008000">exec</span> emacs-i3-integration focus down
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+k <span style="color:#008000">exec</span> emacs-i3-integration focus up
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+l <span style="color:#008000">exec</span> emacs-i3-integration focus right
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Left <span style="color:#008000">exec</span> emacs-i3-integration focus left
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Down <span style="color:#008000">exec</span> emacs-i3-integration focus down
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Up <span style="color:#008000">exec</span> emacs-i3-integration focus up
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Right <span style="color:#008000">exec</span> emacs-i3-integration focus right
|
||
</span></span></code></pre></div><p>The Emacs function has to be called like that:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">my/emacs-i3-windmove</span> <span style="color:#19177c">&#39;right</span>)
|
||
</span></span></code></pre></div><h3 id="move"><code>move</code></h3>
|
||
<p>For the <code>move</code> command I want the following behavior:</p>
|
||
<ul>
|
||
<li>if there is space in the required direction, move the Emacs window there;</li>
|
||
<li>if there is no space in the required direction, but space in the orthogonal directions, move the Emacs window so that there is no more space in the orthogonal directions;</li>
|
||
<li>otherwise, move an X window (which has to be an Emacs frame).</li>
|
||
</ul>
|
||
<p>For the first part, <code>window-swap-states</code> with <code>windmove-find-other-window</code> do well enough.</p>
|
||
<p><code>evil-move-window</code> works well for the second part. By itself it doesn&rsquo;t behave quite like i3, for instance, <code>(evil-move-window 'right)</code> in a three-column split would move the window from the far left side to the far right side (bypassing center). Hence the combination as described here.</p>
|
||
<p>So here is a simple predicate which checks whether there is space in the given direction.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/emacs-i3-direction-exists-p</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">some</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">win</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">dir</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">and</span> <span style="color:#19177c">win</span> (<span style="color:#19177c">not</span> (<span style="color:#00f">window-minibuffer-p</span> <span style="color:#19177c">win</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;width</span> <span style="color:#666">&#39;</span>(<span style="color:#19177c">left</span> <span style="color:#19177c">right</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;height</span> <span style="color:#666">&#39;</span>(<span style="color:#19177c">up</span> <span style="color:#19177c">down</span>)))))
|
||
</span></span></code></pre></div><p>And the implementation of the move command.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/emacs-i3-move-window</span> (<span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">other-window</span> (<span style="color:#19177c">windmove-find-other-window</span> <span style="color:#19177c">dir</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-direction</span> (<span style="color:#19177c">my/emacs-i3-direction-exists-p</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;up</span> <span style="color:#19177c">&#39;width</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;down</span> <span style="color:#19177c">&#39;width</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;left</span> <span style="color:#19177c">&#39;height</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;right</span> <span style="color:#19177c">&#39;height</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">and</span> <span style="color:#19177c">other-window</span> (<span style="color:#19177c">not</span> (<span style="color:#00f">window-minibuffer-p</span> <span style="color:#19177c">other-window</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">window-swap-states</span> (<span style="color:#00f">selected-window</span>) <span style="color:#19177c">other-window</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">other-direction</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">evil-move-window</span> <span style="color:#19177c">dir</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#800">t</span> (<span style="color:#19177c">i3-msg</span> <span style="color:#ba2121">&#34;move&#34;</span> (<span style="color:#00f">symbol-name</span> <span style="color:#19177c">dir</span>))))))
|
||
</span></span></code></pre></div><p>The relevant section of the i3 config:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+h <span style="color:#008000">exec</span> emacs-i3-integration move left
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+j <span style="color:#008000">exec</span> emacs-i3-integration move down
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+k <span style="color:#008000">exec</span> emacs-i3-integration move up
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+l <span style="color:#008000">exec</span> emacs-i3-integration move right
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+Left <span style="color:#008000">exec</span> emacs-i3-integration move left
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+Down <span style="color:#008000">exec</span> emacs-i3-integration move down
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+Up <span style="color:#008000">exec</span> emacs-i3-integration move up
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+Right <span style="color:#008000">exec</span> emacs-i3-integration move right
|
||
</span></span></code></pre></div><h3 id="resize-and-balance-windows"><code>resize</code> and balance windows</h3>
|
||
<p>Next on the line are <code>resize grow</code> and <code>resize shrink</code>. <code>evil-window-</code> functions do nicely for this task.</p>
|
||
<p>This function also checks whether there is space to resize in the given direction with the help of the predicate defined above. The command is forwarded back to i3 if there is not.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/emacs-i3-resize-window</span> (<span style="color:#19177c">dir</span> <span style="color:#19177c">kind</span> <span style="color:#19177c">value</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#008000">or</span> (<span style="color:#19177c">one-window-p</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#19177c">my/emacs-i3-direction-exists-p</span> <span style="color:#19177c">dir</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">i3-msg</span> <span style="color:#ba2121">&#34;resize&#34;</span> (<span style="color:#00f">symbol-name</span> <span style="color:#19177c">kind</span>) (<span style="color:#00f">symbol-name</span> <span style="color:#19177c">dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;%s px or %s ppt&#34;</span> <span style="color:#19177c">value</span> <span style="color:#19177c">value</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">value</span> (<span style="color:#00f">/</span> <span style="color:#19177c">value</span> <span style="color:#666">2</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">kind</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;shrink</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;width</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">evil-window-decrease-width</span> <span style="color:#19177c">value</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;height</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">evil-window-decrease-height</span> <span style="color:#19177c">value</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;grow</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">dir</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;width</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">evil-window-increase-width</span> <span style="color:#19177c">value</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">&#39;height</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">evil-window-increase-height</span> <span style="color:#19177c">value</span>)))))))
|
||
</span></span></code></pre></div><p>Here I&rsquo;m following the default configuration of i3, which creates a &ldquo;submode&rdquo; to resize windows.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mode <span style="color:#ba2121">&#34;resize&#34;</span> <span style="color:#666">{</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> bindsym h <span style="color:#008000">exec</span> emacs-i3-integration resize shrink width <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym j <span style="color:#008000">exec</span> emacs-i3-integration resize grow height <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym k <span style="color:#008000">exec</span> emacs-i3-integration resize shrink height <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym l <span style="color:#008000">exec</span> emacs-i3-integration resize grow width <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+h <span style="color:#008000">exec</span> emacs-i3-integration resize shrink width <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+j <span style="color:#008000">exec</span> emacs-i3-integration resize grow height <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+k <span style="color:#008000">exec</span> emacs-i3-integration resize shrink height <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+l <span style="color:#008000">exec</span> emacs-i3-integration resize grow width <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic"># same bindings, but for the arrow keys</span>
|
||
</span></span><span style="display:flex;"><span> bindsym Left <span style="color:#008000">exec</span> emacs-i3-integration resize shrink width <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Down <span style="color:#008000">exec</span> emacs-i3-integration resize grow height <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Up <span style="color:#008000">exec</span> emacs-i3-integration resize shrink height <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Right <span style="color:#008000">exec</span> emacs-i3-integration resize grow width <span style="color:#666">10</span> px or <span style="color:#666">10</span> ppt
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+Left <span style="color:#008000">exec</span> emacs-i3-integration resize shrink width <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+Down <span style="color:#008000">exec</span> emacs-i3-integration resize grow height <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+Up <span style="color:#008000">exec</span> emacs-i3-integration resize shrink height <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span> bindsym Shift+Right <span style="color:#008000">exec</span> emacs-i3-integration resize grow width <span style="color:#666">100</span> px or <span style="color:#666">100</span> ppt
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> bindsym equal <span style="color:#008000">exec</span> i3-emacs-balance-windows
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic"># back to normal: Enter or Escape</span>
|
||
</span></span><span style="display:flex;"><span> bindsym Return mode <span style="color:#ba2121">&#34;default&#34;</span>
|
||
</span></span><span style="display:flex;"><span> bindsym Escape mode <span style="color:#ba2121">&#34;default&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#666">}</span>
|
||
</span></span></code></pre></div><p>Next, Emacs has a built-in function called <code>balance-windows</code>, but i3 doesn&rsquo;t. Fortunately, there is a Python package called <a href="https://github.com/atreyasha/i3-balance-workspace">i3-balance-workspace</a>, which performs a similar operation with i3&rsquo;s IPC. If you use Guix as I do, I&rsquo;ve written a <a href="https://github.com/SqrtMinusOne/channel-q/blob/master/i3-balance-workspace.scm">package definition</a>.</p>
|
||
<p>So here is a small wrapper which calls <code>i3_balance_workspace</code> and <code>M-x balance-windows</code> if the current window is Emacs.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">if</span> <span style="color:#666">[[</span> <span style="color:#008000;font-weight:bold">$(</span>xdotool getactivewindow getwindowname<span style="color:#008000;font-weight:bold">)</span> <span style="color:#666">=</span>~ ^emacs<span style="color:#666">(</span>:.*<span style="color:#666">)</span>?@.* <span style="color:#666">]]</span>; <span style="color:#008000;font-weight:bold">then</span>
|
||
</span></span><span style="display:flex;"><span> emacsclient -e <span style="color:#ba2121">&#34;(balance-windows)&#34;</span> &amp;
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">fi</span>
|
||
</span></span><span style="display:flex;"><span>i3_balance_workspace
|
||
</span></span></code></pre></div><h3 id="layout-toggle-split"><code>layout toggle split</code></h3>
|
||
<p><a href="https://github.com/emacsorphanage/transpose-frame">transpose-frame</a> is a package to &ldquo;transpose&rdquo; the current Emacs windows layout, which behaves somewhat similar to the <code>layout toggle split</code> command in i3, so I&rsquo;ll use it as well.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">transpose-frame</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:commands</span> (<span style="color:#19177c">transpose-frame</span>))
|
||
</span></span></code></pre></div><p>The i3 config for this command:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+e <span style="color:#008000">exec</span> emacs-i3-integration layout toggle split
|
||
</span></span></code></pre></div><h3 id="the-entrypoint">The entrypoint</h3>
|
||
<p>Finally, the entrypoint for the Emacs integration. In addition to the commands defined above, it processes <code>split</code> and <code>kill</code> commands and passes every other command back to i3.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/emacs-i3-integration</span> (<span style="color:#19177c">command</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">pcase</span> <span style="color:#19177c">command</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">rx</span> <span style="color:#19177c">bos</span> <span style="color:#ba2121">&#34;focus&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/emacs-i3-windmove</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">intern</span> (<span style="color:#00f">elt</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">command</span>) <span style="color:#666">1</span>))))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">rx</span> <span style="color:#19177c">bos</span> <span style="color:#ba2121">&#34;move&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/emacs-i3-move-window</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">intern</span> (<span style="color:#00f">elt</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">command</span>) <span style="color:#666">1</span>))))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#008000">rx</span> <span style="color:#19177c">bos</span> <span style="color:#ba2121">&#34;resize&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/emacs-i3-resize-window</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">intern</span> (<span style="color:#00f">elt</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">command</span>) <span style="color:#666">2</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">intern</span> (<span style="color:#00f">elt</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">command</span>) <span style="color:#666">1</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">string-to-number</span> (<span style="color:#00f">elt</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">command</span>) <span style="color:#666">3</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;layout toggle split&#34;</span> (<span style="color:#19177c">transpose-frame</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;split h&#34;</span> (<span style="color:#19177c">evil-window-split</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;split v&#34;</span> (<span style="color:#19177c">evil-window-vsplit</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;kill&#34;</span> (<span style="color:#19177c">evil-quit</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">-</span> (<span style="color:#19177c">i3-msg</span> <span style="color:#19177c">command</span>))))
|
||
</span></span></code></pre></div><p>The rest of the relevant i3 config to do the splits:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+s <span style="color:#008000">exec</span> emacs-i3-integration split h
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+v <span style="color:#008000">exec</span> emacs-i3-integration split v
|
||
</span></span></code></pre></div><p>And to kill the window:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+Shift+q <span style="color:#008000">exec</span> emacs-i3-integration <span style="color:#008000">kill</span>
|
||
</span></span></code></pre></div><h3 id="switching-i3-tabs">Switching i3 tabs</h3>
|
||
<p>As I use i3&rsquo;s tabbed layout quite extensively, occasionally I want to switch out of the Emacs tab with one button, and that&rsquo;s where my integration may interfere.</p>
|
||
<p>As a workaround, I found a small Rust program called <a href="https://github.com/nikola-kocic/i3-switch-tabs">i3-switch-tabs</a>, which also communicates with i3 via its IPC to switch the top-level tab. I&rsquo;ve written a <a href="https://github.com/SqrtMinusOne/channel-q/blob/master/i3-switch-tabs.scm">Guix package definition</a> for that as well.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+period <span style="color:#008000">exec</span> i3-switch-tabs right
|
||
</span></span><span style="display:flex;"><span>bindsym <span style="color:#19177c">$mod</span>+comma <span style="color:#008000">exec</span> i3-switch-tabs left
|
||
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
|
||
<p>So, how does all of that feel? Actually, I got used to that setup pretty quickly. Using <code>&lt;s-Q&gt;</code> to quit windows and the <code>&lt;s-r&gt;</code> submode to resize them is particularly nice. I&rsquo;ve seen people making hydras in Emacs to do the latter.</p>
|
||
<p>All of that would probably be easier to do in a WM which is configured in a programming language rather than in a self-cooked DSL, so I may try to replicate that somewhere else in an unknown time in the future. Meanwhile, it&rsquo;s pretty good.</p>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>My EMMS and elfeed setup</title>
|
||
<link>https://sqrtminusone.xyz/posts/2021-09-07-emms/</link>
|
||
<pubDate>Wed, 08 Sep 2021 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2021-09-07-emms/</guid>
|
||
<content type="html">
|
||
<h2 id="intro">Intro</h2>
|
||
<figure><img src="https://sqrtminusone.xyz/images/emms/emms-screenshot.png"/>
|
||
</figure>
|
||
|
||
<p>This is the current state of my quest to live in Emacs, at least in part of reading RSS and music.</p>
|
||
<p>Even before I lost my mind about customizing obscure keyboard-driven software, I tried Inoreader, self-hosted FreshRSS, and then newsboat from the RSS side and ncmpcpp+MPD from the audio player side. At some point, I got curious about whether I can do the same in Emacs.</p>
|
||
<p>The respective emacs packages, elfeed and EMMS, proved somewhat tricky to set up, i.e. I had to figure out the source code in both cases. I even submitted a small patch to EMMS to make it parse my MPD library correctly.</p>
|
||
<p>But in the end, only extensive customization capacities of Emacs enabled me to make a setup where these parts nicely come together and do more or less exactly what I want. However, this means there are a lot of degrees of freedom involved, so I’ll try to cover the important parts and link to the original sources wherever possible.</p>
|
||
<p>I’d call it “workflow”, but the “work” part does not quite catch the point here.</p>
|
||
<h2 id="mpd">MPD</h2>
|
||
<p>So, we have to start somewhere.</p>
|
||
<p><a href="https://www.musicpd.org/">MPD</a> is a server for playing music, although it is usually hosted on the local machine, i.e. the one on which you intend to listen to music. There is <a href="https://www.musicpd.org/clients/">bunch of clients</a> available (take a look at <a href="https://github.com/ncmpcpp/ncmpcpp">ncmpcpp</a> is you like terminal-based apps), but here our point of interest is its integration with EMMS.</p>
|
||
<p>While EMMS is capable of playing music without it, MPD has the advantage of being independent of Emacs. That means it won&rsquo;t close if Emacs crashes and it can be controlled more easily with other means.</p>
|
||
<p>MPD configuration is a pretty easy process. First, install MPD and <a href="https://www.musicpd.org/clients/mpc/">mpc</a> (a minimal MPD CLI client) from your distribution&rsquo;s package repository. After doing that, you&rsquo;d have to create a config file at the location <code>~/.config/mpd/mpd.conf</code>. Mine looks something like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-vim" data-lang="vim"><span style="display:flex;"><span>music_directory <span style="color:#ba2121">&#34;~/Music&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>playlist_directory <span style="color:#ba2121">&#34;~/.mpd/playlists&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>db_file <span style="color:#ba2121">&#34;~/.mpd/database&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>log_file <span style="color:#ba2121">&#34;~/.mpd/log&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>pid_file <span style="color:#ba2121">&#34;~/.mpd/pid&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>state_file <span style="color:#ba2121">&#34;~/.mpd/state&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>sticker_file <span style="color:#ba2121">&#34;~/.mpd/sticker.sql&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>audio_output {<span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span> type <span style="color:#ba2121">&#34;pulse&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span> name <span style="color:#ba2121">&#34;My Pulse Output&#34;</span><span style="">
|
||
</span></span></span><span style="display:flex;"><span><span style=""></span>}<span style="">
|
||
</span></span></span></code></pre></div><p>Here <code>music_directory</code> is, well, a directory in which MPD will look for music files. Take a look at <a href="https://linux.die.net/man/5/mpd.conf">man mpd.conf</a> and <a href="https://github.com/MusicPlayerDaemon/MPD/blob/master/doc/mpdconf.example">the default config example</a> for more information.</p>
|
||
<p>Because MPD is a daemon, it has to be started in order to work. The easiest way is to add <code>mpd</code> to your init system, e.g. with GNU Shepherd:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scheme" data-lang="scheme"><span style="display:flex;"><span>(<span style="color:#008000;font-weight:bold">define </span><span style="color:#19177c">mpd</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">make</span> <span style="color:#19177c">&lt;service&gt;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">#</span><span style="color:#19177c">:provides</span> <span style="color:#666">&#39;</span>(<span style="color:#19177c">mpd</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">#</span><span style="color:#19177c">:respawn?</span> <span style="color:#800">#t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">#</span><span style="color:#19177c">:start</span> (<span style="color:#00f">make-forkexec-constructor</span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;mpd&#34;</span> <span style="color:#ba2121">&#34;--no-daemon&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">#</span><span style="color:#19177c">:stop</span> (<span style="color:#00f">make-kill-destructor</span>)))
|
||
</span></span></code></pre></div><p>You can also launch <code>mpd</code> manually, as it will daemonize itself by default. To check if MPD is working, run <code>mpc status</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mpc status
|
||
</span></span></code></pre></div><p>Take a look at <a href="https://mpd.readthedocs.io/en/stable/user.html#configuration">the official documentation</a> for more information on this subject.</p>
|
||
<h3 id="music-directory">Music directory</h3>
|
||
<p>The next question after we&rsquo;ve set up MPD is how to organize the music directory.</p>
|
||
<p>MPD itself is not too concerned about the structure of your <code>music_directory</code>, because it uses audio tags to classify the files. However, if you want to have album covers in EMMS, you need to have one folder per one album. Otherwise and in other respects, the structure can be arbitrary.</p>
|
||
<p>So we need to tag the audio files. My favorite audio-tagging software is <a href="https://picard.musicbrainz.org/">MusicBrainz Picard</a>, which can set tags automatically even if the file has no metadata at all, and move the file automatically according to the configuration. The aforementioned ncmpcpp also has a decent tag editor; finally, there is a simple <a href="https://mutagen.readthedocs.io/en/latest/man/mid3v2.html">mutagen-based CLI tool</a>.</p>
|
||
<h2 id="emms">EMMS</h2>
|
||
<p><a href="https://www.gnu.org/software/emms/">EMMS</a> is the Emacs Multimedia System, a package that can get play stuff from various sources using various players. It is a part of Emacs, which means you can use the built-in version, but the git version has a few useful patches, so I advise using the latter.</p>
|
||
<p>Install it however you usually install packages in Emacs; I use use-package + straight:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">emms</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>)
|
||
</span></span></code></pre></div><h3 id="setup-and-mpd-integration">Setup &amp; MPD integration</h3>
|
||
<p>Now we have to configure EMMS. The following expressions have to be executed after EMMS is loaded, which means we can add them to the <code>:config</code> section of the <code>use-package</code> expression above.</p>
|
||
<p>First, EMMS exposes a handy function that loads all the stable EMMS features. You can take a look at its source and pick the features you need or load everything like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">require</span> <span style="color:#19177c">&#39;emms-setup</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">emms-all</span>)
|
||
</span></span></code></pre></div><p>Then we need to set up a directory for EMMS files and the required parameters for <code>emms-player-mpd</code>. Note that <code>emms-player-mpd-music-directory</code> should be set to the same value as <code>music_directory</code> in <code>mpd.conf</code>.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">emms-source-file-default-directory</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/Music/&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">emms-player-mpd-server-name</span> <span style="color:#ba2121">&#34;localhost&#34;</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">emms-player-mpd-server-port</span> <span style="color:#ba2121">&#34;6600&#34;</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">emms-player-mpd-music-directory</span> <span style="color:#ba2121">&#34;~/Music&#34;</span>)
|
||
</span></span></code></pre></div><p>Add the required functions to EMMS lists:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">add-to-list</span> <span style="color:#19177c">&#39;emms-info-functions</span> <span style="color:#19177c">&#39;emms-info-mpd</span>)
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">add-to-list</span> <span style="color:#19177c">&#39;emms-player-list</span> <span style="color:#19177c">&#39;emms-player-mpd</span>)
|
||
</span></span></code></pre></div><p>Now we can connect EMMS to MPD. For some reason, executing this function stops the MPD playback, but it is not a big issue because it has to be executed only once.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">emms-player-mpd-connect</span>)
|
||
</span></span></code></pre></div><p>The last thing we may want is to link EMMS playlist clearing to MPD playlist clearing. I&rsquo;m not sure how this interacts with MPD&rsquo;s own playlists because I don&rsquo;t use them, so you may need to watch out here if you do.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;emms-playlist-cleared-hook</span> <span style="color:#19177c">&#39;emms-player-mpd-clear</span>)
|
||
</span></span></code></pre></div><h3 id="usage">Usage</h3>
|
||
<p>One rough edge of EMMS &amp; MPD integration is that EMMS and MPD have separate libraries and playlists.</p>
|
||
<p>So, first we have to populate the MPD library with <code>M-x emms-player-mpd-update-all</code>. This operation is executed asynchronously by MPD and may take a few minutes for the first run. The subsequent runs are much faster. You can do the same by invoking <code>mpc update</code> from the command line.</p>
|
||
<p>Second, we have to populate the EMMS library (cache) from the MPD library. To do that, run <code>M-x emms-cache-set-all-from-mpd</code>. If something went wrong with the EMMS cache, you always can clean it with <code>M-x emms-cache-reset</code>.</p>
|
||
<p>After this is done, we can finally play music! To do that, run <code>M-x emms-browser</code>. The left window should have the EMMS browser buffer with the loaded library, the right one should contain (as for now empty) playlist.</p>
|
||
<p>In the browser we can use the following commands to add elements to the playlist:</p>
|
||
<ul>
|
||
<li><code>M-x emms-browser-toggle-subitems</code> (<code>&lt;tab&gt;</code> in evil, <code>SPC</code> in vanilla) to open/close the element under cursor</li>
|
||
<li><code>M-x emms-browser-add-tracks</code> (<code>RET</code> in both styles) to add the element under the cursor to the playlist</li>
|
||
</ul>
|
||
<p>Now, we have a few tracks in the EMMS playlist, but they are not in the MPD playlist yet.</p>
|
||
<p>In the EMMS playlist buffer, <code>M-x emms-playlist-mode-play-smart</code> (<code>RET</code>) will sync the playlists and start playing the song under the cursor. Also, use</p>
|
||
<ul>
|
||
<li><code>M-x emms-playlist-mode-kill-track</code> (<code>D</code>) to remove the element under cursor</li>
|
||
<li><code>M-x emms-playlist-clear</code> (<code>C</code>) to clear the playlist. With the hook from the previous section this should also clear the MPD playlist.</li>
|
||
</ul>
|
||
<p>Take a look at the <a href="https://www.gnu.org/software/emms/manual/">EMMS manual</a> for more information, including sections about <a href="https://www.gnu.org/software/emms/manual/#Interactive-Playlists">playlist</a> and <a href="https://www.gnu.org/software/emms/manual/#The-Browser">browser.</a></p>
|
||
<h3 id="fetching-lyrics">Fetching lyrics</h3>
|
||
<p>One feature of ncmpcpp I was missing here is fetching lyrics, so I&rsquo;ve written a small package to do just that.</p>
|
||
<p>Debugging the package turned out to be quite funny because apparently, there is no way around parsing HTML with this task. So I&rsquo;ve chosen genius.com as the source, but the site turned out to provide different versions of itself (with different DOMs!) to different users.</p>
|
||
<p>At any rate, I&rsquo;ve processed the cases I found, and it seems to be working, at least for me. To use the package, <a href="https://genius.com/api-clients/new">get the API key</a> from Genius and install it:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">lyrics-fetcher</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:after</span> (<span style="color:#19177c">emms</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">lyrics-fetcher-genius-access-token</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">password-store-get</span> <span style="color:#ba2121">&#34;My_Online/APIs/genius.com&#34;</span>)))
|
||
</span></span></code></pre></div><p>To fetch lyrics for the current playing EMMS song, run <code>M-x lyrics-fetcher-show-lyrics</code>. Or run <code>M-x lyrics-fetcher-emms-browser-show-at-point</code> to fetch data for the current point in the EMMS browser. See <a href="https://github.com/SqrtMinusOne/lyrics-fetcher.el">the package homepage</a> for more information.</p>
|
||
<h3 id="album-covers">Album covers</h3>
|
||
<p>I&rsquo;ve mentioned above that EMMS supports displaying album covers.</p>
|
||
<p>For this to work, it is necessary to have one album per one folder. By default the cover image should be saved to images named <code>cover_small</code> (100x100 recommended), <code>cover_medium</code> (200x200 recommended) and <code>cover_large</code>. The small version is to be displayed in the EMMS browser, the medium one in the playlist.</p>
|
||
<p>It&rsquo;s not required for images to be exactly of these sizes, but they definitely should be of one size across different albums to look nice in the interface.</p>
|
||
<p>You can resize images with ImageMagick with commands like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>convert cover.jpg -resize 100x100^ -gravity Center -extent 100x100 cover_small.jpg
|
||
</span></span><span style="display:flex;"><span>convert cover.jpg -resize 200x200^ -gravity Center -extent 200x200 cover_medium.jpg
|
||
</span></span></code></pre></div><p><code>lyrics-fetcher</code> can (try to) do this automatically by downloading the cover from genius.com with <code>M-x lyrics-fetcher-emms-browser-fetch-covers-at-point</code> in EMMS browser.</p>
|
||
<h2 id="mpv-and-youtube">MPV and YouTube</h2>
|
||
<p><a href="https://mpv.io/">MPV</a> is an extensible media player, which integrates with <a href="https://github.com/ytdl-org/youtube-dl">youtube-dl</a> and is controllable by EMMS, thus quite fitting for this setup.</p>
|
||
<h3 id="mpv-and-youtube-dl">MPV and youtube-dl</h3>
|
||
<p>First, install both <code>mpv</code> and <code>youtube-dl</code> from your distribution&rsquo;s package repository.</p>
|
||
<p>Then we can add another player to the list:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">add-to-list</span> <span style="color:#19177c">&#39;emms-player-list</span> <span style="color:#19177c">&#39;emms-player-mpv</span> <span style="color:#800">t</span>)
|
||
</span></span></code></pre></div><p>EMMS determines which player to use by a regexp. <code>emms-player-mpd</code> sets the default regexp from MPD&rsquo;s diagnostic output so that regex opens basically everything, including videos, HTTPS links, etc. That is fine if MPD is the only player in EMMS, but as we want to use MPV as well, we need to override the regexes.</p>
|
||
<p>MPD regexp can look like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">emms-player-set</span> <span style="color:#19177c">emms-player-mpd</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;regex</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">emms-player-simple-regexp</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;m3u&#34;</span> <span style="color:#ba2121">&#34;ogg&#34;</span> <span style="color:#ba2121">&#34;flac&#34;</span> <span style="color:#ba2121">&#34;mp3&#34;</span> <span style="color:#ba2121">&#34;wav&#34;</span> <span style="color:#ba2121">&#34;mod&#34;</span> <span style="color:#ba2121">&#34;au&#34;</span> <span style="color:#ba2121">&#34;aiff&#34;</span>))
|
||
</span></span></code></pre></div><p>And a regexp for MPV to open videos and youtube URLs:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">emms-player-set</span> <span style="color:#19177c">emms-player-mpv</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;regex</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">rx</span> (<span style="color:#008000">or</span> (<span style="color:#666">:</span> <span style="color:#ba2121">&#34;https://&#34;</span> (<span style="color:#00f">*</span> <span style="color:#19177c">nonl</span>) <span style="color:#ba2121">&#34;youtube.com&#34;</span> (<span style="color:#00f">*</span> <span style="color:#19177c">nonl</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">+</span> (<span style="color:#ba2121">? </span>(<span style="color:#008000">or</span> <span style="color:#ba2121">&#34;https://&#34;</span> <span style="color:#ba2121">&#34;http://&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">*</span> <span style="color:#19177c">nonl</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">regexp</span> (<span style="color:#00f">eval</span> (<span style="color:#19177c">emms-player-simple-regexp</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;mp4&#34;</span> <span style="color:#ba2121">&#34;mov&#34;</span> <span style="color:#ba2121">&#34;wmv&#34;</span> <span style="color:#ba2121">&#34;webm&#34;</span> <span style="color:#ba2121">&#34;flv&#34;</span> <span style="color:#ba2121">&#34;avi&#34;</span> <span style="color:#ba2121">&#34;mkv&#34;</span>)))))))
|
||
</span></span></code></pre></div><p>Then, by default youtube-dl plays the video in the best possible quality, which may be pretty high. To have some control over it, we can modify the <code>--ytdl-format</code> key in the <code>emms-player-mpv-parameters</code> variable. I&rsquo;ve come up with the following solution:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/youtube-dl-quality-list</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;bestvideo[height&lt;=720]+bestaudio/best[height&lt;=720]&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;bestvideo[height&lt;=480]+bestaudio/best[height&lt;=480]&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;bestvideo[height&lt;=1080]+bestaudio/best[height&lt;=1080]&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/default-emms-player-mpv-parameters</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;--quiet&#34;</span> <span style="color:#ba2121">&#34;--really-quiet&#34;</span> <span style="color:#ba2121">&#34;--no-audio-display&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/set-emms-mpd-youtube-quality</span> (<span style="color:#19177c">quality</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span> <span style="color:#ba2121">&#34;P&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> <span style="color:#19177c">quality</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">quality</span> (<span style="color:#00f">completing-read</span> <span style="color:#ba2121">&#34;Quality: &#34;</span> <span style="color:#19177c">my/youtube-dl-quality-list</span> <span style="color:#800">nil</span> <span style="color:#800">t</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">emms-player-mpv-parameters</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>(<span style="color:#666">,@</span><span style="color:#19177c">my/default-emms-player-mpv-parameters</span> <span style="color:#666">,</span>(<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;--ytdl-format=%s&#34;</span> <span style="color:#19177c">quality</span>))))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#19177c">my/set-emms-mpd-youtube-quality</span> (<span style="color:#00f">car</span> <span style="color:#19177c">my/youtube-dl-quality-list</span>))
|
||
</span></span></code></pre></div><p>Run <code>M-x my/set-emms-mpd-youtube-quality</code> to pick the required quality. Take a look at <a href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#format-selection">youtube-dl docs</a> for more information about the format selection.</p>
|
||
<p>Now <code>M-x emms-add-url</code> should work on YouTube URLs just fine. Just keep in mind that it will only add the URL to the playlist, not play it right away.</p>
|
||
<h3 id="cleanup-emms-cache">Cleanup EMMS cache</h3>
|
||
<p>All the added URLs stay in the EMMS cache after being played. We probably don&rsquo;t want them to remain there, so here is a function to remove URLs from the EMMS cache.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/emms-cleanup-urls</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">keys-to-delete</span> <span style="color:#666">&#39;</span>()))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">maphash</span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">key</span> <span style="color:#19177c">value</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">eq</span> (<span style="color:#00f">cdr</span> (<span style="color:#00f">assoc</span> <span style="color:#19177c">&#39;type</span> <span style="color:#19177c">value</span>)) <span style="color:#19177c">&#39;url</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">add-to-list</span> <span style="color:#19177c">&#39;keys-to-delete</span> <span style="color:#19177c">key</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">emms-cache-db</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">key</span> <span style="color:#19177c">keys-to-delete</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">remhash</span> <span style="color:#19177c">key</span> <span style="color:#19177c">emms-cache-db</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">emms-cache-dirty</span> <span style="color:#800">t</span>))
|
||
</span></span></code></pre></div><h2 id="youtube-rss">YouTube RSS</h2>
|
||
<h3 id="where-to-get-urls">Where to get URLs?</h3>
|
||
<p>So, we are able to watch YouTube videos by URLs, but where to get URLs from? A natural solution is to use <a href="https://github.com/skeeto/elfeed">elfeed</a> and RSS feeds.</p>
|
||
<p>I&rsquo;ve tried a bunch of options to get feeds for YouTube channels. The first one is <a href="https://api.invidious.io/">Invidious</a>, a FOSS YouTube frontend. The problem here is that various instances I tried weren&rsquo;t particularly stable (at least when I was using them) and hosting the thing by myself would be overkill. And switching instances is causing duplicate entries in the Elfeed DB.</p>
|
||
<p>The second option is to use YouTube&rsquo;s own RSS. The feed URL looks like <code>https://www.youtube.com/feeds/videos.xml?channel_id=&lt;CHANNEL_ID&gt;=</code>. <a href="https://stackoverflow.com/questions/14366648/how-can-i-get-a-channel-id-from-youtube">Here are</a> a couple of options of figuring out <code>CHANNEL_ID</code> in case it&rsquo;s not easily available. The problem with YouTube RSS is that it uses fields that are not supported by elfeed, so the feed entry lacks a preview and description.</p>
|
||
<p>As my workaround, I&rsquo;ve written a small <a href="https://github.com/SqrtMinusOne/yt-rss">web-server</a> which converts an RSS feed from YouTube to an elfeed-compatible Atom feed. It doesn&rsquo;t do much, so you can just download the thing and launch it:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://github.com/SqrtMinusOne/yt-rss.git
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">cd</span> ./yt-rss
|
||
</span></span><span style="display:flex;"><span>pip install -r requirements.txt
|
||
</span></span><span style="display:flex;"><span>gunicorn main:app
|
||
</span></span></code></pre></div><p>A feed for a particular channel will be available at</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>http://localhost:8000/&lt;channel_id&gt;?token=&lt;token&gt;
|
||
</span></span></code></pre></div><p>where <code>&lt;token&gt;</code> is set in <code>.env</code> file to the default value of <code>12345</code>.</p>
|
||
<h3 id="elfeed">Elfeed</h3>
|
||
<p><a href="https://github.com/skeeto/elfeed">Elfeed</a> is an Emacs Atom &amp; RSS reader. It&rsquo;s a pretty popular package with lots of information written over the years, so I&rsquo;ll cover just my particular setup.</p>
|
||
<p>My elfeed config, sans keybindings, looks like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">elfeed</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:commands</span> (<span style="color:#19177c">elfeed</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">elfeed-db-directory</span> <span style="color:#ba2121">&#34;~/.elfeed&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">elfeed-enclosure-default-dir</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/Downloads&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">advice-add</span> <span style="color:#00f">#&#39;</span><span style="color:#19177c">elfeed-insert-html</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:around</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">fun</span> <span style="color:#008000">&amp;rest</span> <span style="color:#19177c">r</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">shr-use-fonts</span> <span style="color:#800">nil</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">apply</span> <span style="color:#19177c">fun</span> <span style="color:#19177c">r</span>)))))
|
||
</span></span></code></pre></div><p>The advice there forces elfeed to use monospace fonts in the show buffer.</p>
|
||
<p>I also use <a href="https://github.com/remyhonig/elfeed-org">elfeed-org</a>, which gives an option to store the feed config in an <code>.org</code> file instead of a variable:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">elfeed-org</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:after</span> (<span style="color:#19177c">elfeed</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">rmh-elfeed-org-files</span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;~/.emacs.d/elfeed.org&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-org</span>))
|
||
</span></span></code></pre></div><p>So, however you&rsquo;ve got URLs for YouTube channels, put them into elfeed.</p>
|
||
<p>To fetch the feeds, open elfeed with <code>M-x elfeed</code> and run <code>M-x elfeed-search-fetch</code> in the search buffer. And as usual, take a look at <a href="https://github.com/skeeto/elfeed">the package documentation</a> for more information.</p>
|
||
<p>To help with navigating through the long list of entries, I&rsquo;ve made the following function to narrow the search buffer to the feed of the entry under cursor:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/elfeed-search-filter-source</span> (<span style="color:#19177c">entry</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Filter elfeed search buffer by the feed under cursor.&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span> (<span style="color:#00f">list</span> (<span style="color:#19177c">elfeed-search-selected</span> <span style="color:#008000">:ignore-region</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#19177c">elfeed-entry-p</span> <span style="color:#19177c">entry</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-search-set-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;@6-months-ago &#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;+unread &#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;=&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">replace-regexp-in-string</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">rx</span> <span style="color:#ba2121">&#34;?&#34;</span> (<span style="color:#00f">*</span> <span style="color:#19177c">not-newline</span>) <span style="color:#19177c">eos</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-feed-url</span> (<span style="color:#19177c">elfeed-entry-feed</span> <span style="color:#19177c">entry</span>)))))))
|
||
</span></span></code></pre></div><p>So I mostly alternate between <code>M-x my/elfeed-search-filter-source</code> and <code>M-x elfeed-search-clear-filter</code>. I tag the entries which I want to watch later with <code>+later</code>, and add the ones I want to watch right now to the playlist.</p>
|
||
<h3 id="integrating-with-emms">Integrating with EMMS</h3>
|
||
<p>Finally, here&rsquo;s the solution I came up with to add an entry from elfeed to the EMMS playlist. First, we&rsquo;ve got to get a URL:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/get-youtube-url</span> (<span style="color:#19177c">link</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">watch-id</span> (<span style="color:#19177c">cadr</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">assoc</span> <span style="color:#ba2121">&#34;watch?v&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">url-parse-query-string</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">substring</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">url-filename</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">url-generic-parse-url</span> <span style="color:#19177c">link</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">1</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span> <span style="color:#ba2121">&#34;https://www.youtube.com/watch?v=&#34;</span> <span style="color:#19177c">watch-id</span>)))
|
||
</span></span></code></pre></div><p>This function is intended to work with both Invidious and YouTube RSS feeds. Of course, it will require some adaptation if you want to watch channels from something like PeerTube or Odysee.</p>
|
||
<p>The easiest way to put the URL to the playlist is to define a new source for EMMS:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">define-emms-source</span> <span style="color:#19177c">elfeed</span> (<span style="color:#19177c">entry</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">track</span> (<span style="color:#19177c">emms-track</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;url</span> (<span style="color:#19177c">my/get-youtube-url</span> (<span style="color:#19177c">elfeed-entry-link</span> <span style="color:#19177c">entry</span>)))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">emms-track-set</span> <span style="color:#19177c">track</span> <span style="color:#19177c">&#39;info-title</span> (<span style="color:#19177c">elfeed-entry-title</span> <span style="color:#19177c">entry</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">emms-playlist-insert-track</span> <span style="color:#19177c">track</span>)))
|
||
</span></span></code></pre></div><p>Because <code>define-emms-source</code> is an EMMS macro, the code block above has to be evaluated with EMMS loaded. E.g. you can wrap it into <code>(with-eval-after-load 'emms ...)</code> or put in the <code>:config</code> section.</p>
|
||
<p>The macro defines a bunch of functions to work with the source, which we can use in another function:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/elfeed-add-emms-youtube</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">emms-add-elfeed</span> <span style="color:#19177c">elfeed-show-entry</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-tag</span> <span style="color:#19177c">elfeed-show-entry</span> <span style="color:#19177c">&#39;watched</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">elfeed-show-refresh</span>))
|
||
</span></span></code></pre></div><p>Now, calling <code>M-x my/elfeed-add-emms-youtube</code> in the <code>*elfeed-show*</code> buffer will add the correct URL to the playlist and tag the entry with <code>+watched</code>. I&rsquo;ve bound the function to <code>gm</code>.</p>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>Replacing Jupyter Notebook with Org Mode</title>
|
||
<link>https://sqrtminusone.xyz/posts/2021-05-01-org-python/</link>
|
||
<pubDate>Sat, 01 May 2021 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2021-05-01-org-python/</guid>
|
||
<content type="html">
|
||
<figure><img src="https://sqrtminusone.xyz/images/org-python/org-python-screenshot.png"/>
|
||
</figure>
|
||
|
||
<h2 id="why">Why?</h2>
|
||
<p><a href="https://jupyter-notebook.readthedocs.io/en/stable/">Jupyter Notebook</a> and its successor <a href="https://jupyterlab.readthedocs.io/en/stable/">Jupyter Lab</a> providing an interactive development environment for many programming languages are in lots of ways great pieces of software.</p>
|
||
<p>But while I was using the former, and then the latter, I was also an as-full-time-as-one-can-get NeoVim user. &ldquo;As one can get&rdquo; is because, of course, there is no sensible way to extend the NeoVim editing experience to the Jupyter ecosystem.</p>
|
||
<p>A possibility for change appeared with my discovery of Emacs not so long ago. Emacs, a substantially more extensible piece of software, potentially can be used for the mentioned kind of programming. So I decided to try.</p>
|
||
<p>Sometime past that decision, it&rsquo;s time to wrap up the results. To start with, I&rsquo;ll briefly discuss the pros &amp; cons of using Org mode rather than Jupyter Notebook/Lab. Here is my list of advantages:</p>
|
||
<ul>
|
||
<li>Emacs, at least for me, is way more comfortable to use than a browser</li>
|
||
<li>Org mode allows using multiple programming languages in one file or multiple sessions with one programming language</li>
|
||
<li>Richer &amp; way more flexible export &amp; tangle capacities</li>
|
||
<li>More reasonable version control because org mode is just plain text, contrary to Jupyter&rsquo;s JSONs</li>
|
||
</ul>
|
||
<p>The first point deserves to be spelled out with more detail. To start with, Emacs is an objectively better text editor than Jupyter, as Emacs offers a considerable superset of Jupyter&rsquo;s features concerning just writing and editing text. The farthest one can go with Jupyter Lab is to install a vim emulation plugin, which still isn&rsquo;t as good as Evil mode.</p>
|
||
<p>The fact that Emacs can be used for different purposes also helps. For instance, I often write LaTeX documents, which are loosely based on the nearby code, e.g. using some generated charts or tables. Switching an Emacs buffer is easier than switching between Emacs and browser, not to mention that Emacs buffers usually have the same set of keybindings.</p>
|
||
<p>Emacs&rsquo; buffer management system, which is good enough for a window manager, is barely comparable to the tabs of Jupyter Lab. And so on.</p>
|
||
<p>As for why one may want to use Jupyter instead, here is my take on cons:</p>
|
||
<ul>
|
||
<li>Potential performance issues</li>
|
||
<li>The output is not as rich as in the browser</li>
|
||
<li>Collaboration with non-Emacs users is somewhat complicated</li>
|
||
</ul>
|
||
<p>Separation of kernels, server, and client together with non-blocking JavaScript-based UI is a good argument for using Jupyter. And it certainly won&rsquo;t be a problem for a browser to suddenly print a line a million characters long.</p>
|
||
<p>As for the richness of the output, while there are ways to work around the limitations of Emacs there, in some cases the best thing one can do is open the output in question with a browser. I&rsquo;ll discuss doing that further below.</p>
|
||
<p>The collaboration issue can be alleviated with rich export capabilities, but if someone wants to change something in your Org file, execute it and send it back to you, they have to use Emacs.</p>
|
||
<h2 id="basic-setup">Basic setup</h2>
|
||
<p>The core package to this whole venture is <a href="https://github.com/nnicandro/emacs-jupyter">emacs-jupyter</a> (another notable alternative <a href="https://github.com/millejoh/emacs-ipython-notebook">ein</a>, using which can help with the collaboration problem).</p>
|
||
<p>Install it however you install packages in Emacs, here is my preferred way with <code>use-package</code> and <code>straight.el</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">jupyter</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>)
|
||
</span></span></code></pre></div><p>Then, we have to enable languages for <code>org-babel</code>. Put the following in your org mode config section:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">org-babel-do-load-languages</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">&#39;org-babel-load-languages</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>((<span style="color:#19177c">emacs-lisp</span> <span style="color:#666">.</span> <span style="color:#800">t</span>) <span style="color:#408080;font-style:italic">;; Other languages</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">shell</span> <span style="color:#666">.</span> <span style="color:#800">t</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Python &amp; Jupyter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">python</span> <span style="color:#666">.</span> <span style="color:#800">t</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">jupyter</span> <span style="color:#666">.</span> <span style="color:#800">t</span>)))
|
||
</span></span></code></pre></div><p>Now, you should be able to use source blocks with names like <code>jupyter-LANG</code>, e.g. <code>jupyter-python</code>. To use just <code>LANG</code> src blocks, call the following function after <code>org-babel-do-load-languages</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">org-babel-jupyter-override-src-block</span> <span style="color:#ba2121">&#34;python&#34;</span>)
|
||
</span></span></code></pre></div><p>That overrides the built-in <code>python</code> block with <code>jupyter-python</code>.</p>
|
||
<p>If you use <a href="https://github.com/astahlman/ob-async">ob-async</a>, you have to set <code>jupyter-LANG</code> blocks as ignored by this package, because emacs-jupyter has async execution of its own.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">ob-async-no-async-languages-alist</span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;python&#34;</span> <span style="color:#ba2121">&#34;jupyter-python&#34;</span>))
|
||
</span></span></code></pre></div><h2 id="environments">Environments</h2>
|
||
<p>So, we&rsquo;ve set up a basic emacs-jupyter configuration.</p>
|
||
<p>The catch here is that Jupyter should be available on Emacs startup (at the time of evaluation of the <code>emacs-jupyter</code> package, to be precise). That means, if you are launching Emacs with something like an application launcher, global Python &amp; Jupyter will be used.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">import</span> <span style="color:#00f;font-weight:bold">sys</span>
|
||
</span></span><span style="display:flex;"><span>sys<span style="color:#666">.</span>executable
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/usr/bin/python3
|
||
</span></span></code></pre></div><p>Which is probably not what we want. To resolve that, we have to make the right Python available at the required time.</p>
|
||
<h3 id="anaconda">Anaconda</h3>
|
||
<p>If you were using Jupyter Lab or Notebook before, there is a good chance you install it via <a href="https://anaconda.org/">Anaconda</a>. If not, in a nutshell, it is a package &amp; environment manager, which specializes in Python &amp; R, but also supports a whole lot of stuff like Node.js. In my opinion, it is the easiest way to manage multiple Python installations if you don&rsquo;t use some advanced package manager like Guix.</p>
|
||
<p>As one may expect, there is an Emacs package called <a href="https://github.com/necaris/conda.el">conda.el</a> to help working with conda environments in Emacs. We have to put it somewhere before the <code>emacs-jupyter</code> package and call <code>conda-env-activate</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">conda</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">conda-anaconda-home</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/Programs/miniconda3/&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">conda-env-home-directory</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/Programs/miniconda3/&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">conda-env-subdirectory</span> <span style="color:#ba2121">&#34;envs&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">unless</span> (<span style="color:#19177c">getenv</span> <span style="color:#ba2121">&#34;CONDA_DEFAULT_ENV&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">conda-env-activate</span> <span style="color:#ba2121">&#34;base&#34;</span>))
|
||
</span></span></code></pre></div><p>If you have Anaconda installed on a custom path, as I do, you&rsquo;d have to add these <code>setq</code> lines in the <code>:config</code> section. Also, there is no point in activating the environment if Emacs is somehow already launched in an environment.</p>
|
||
<p>That&rsquo;ll give us Jupyter from a base conda environment.</p>
|
||
<p>If you use a plain virtual environment, you can use <a href="https://github.com/porterjamesj/virtualenvwrapper.el">virtualenvwrapper.el</a>, which is similar in its design to conda.el (or, rather, the other way round).</p>
|
||
<h3 id="switching-an-environment">Switching an environment</h3>
|
||
<p>However, as you will notice rather soon, <code>emacs-jupyter</code> will always use the Python kernel found on startup. So if you switch to a new environment, the code will still be running in the old one, which is not too convenient.</p>
|
||
<p>Fortunately, to fix that we have only to force the refresh of Jupyter kernelspecs:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/jupyter-refresh-kernelspecs</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Refresh Jupyter kernelspecs&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">jupyter-available-kernelspecs</span> <span style="color:#800">t</span>))
|
||
</span></span></code></pre></div><p>Calling <code>M-x my/jupyter-refresh-kernelspecs</code> after an environment switch will give you a new kernel. Just keep in mind that a kernelspec seems to be attached to a session, so you&rsquo;d also have to change the session name to get a new kernel.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">import</span> <span style="color:#00f;font-weight:bold">sys</span>
|
||
</span></span><span style="display:flex;"><span>sys<span style="color:#666">.</span>executable
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/home/pavel/Programs/miniconda3/bin/python
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">conda-env-activate</span> <span style="color:#ba2121">&#34;ann&#34;</span>)
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">import</span> <span style="color:#00f;font-weight:bold">sys</span>
|
||
</span></span><span style="display:flex;"><span>sys<span style="color:#666">.</span>executable
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/home/pavel/Programs/miniconda3/bin/python
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">my/jupyter-refresh-kernelspecs</span>)
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">import</span> <span style="color:#00f;font-weight:bold">sys</span>
|
||
</span></span><span style="display:flex;"><span>sys<span style="color:#666">.</span>executable
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/home/pavel/Programs/miniconda3/envs/ann/bin/python
|
||
</span></span></code></pre></div><h2 id="programming">Programming</h2>
|
||
<p>To test if everything is working correctly, run <code>M-x jupyter-run-repl</code>, which should give you a REPL with a chosen kernel. If so, we can finally start using Python in org mode.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python :session hello :async yes
|
||
</span></span><span style="display:flex;"><span>print(&#39;Hello, world!&#39;)
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>: Hello, world!
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span></code></pre></div><p>To avoid repeating similar arguments for the src block, we can set the <code>header-args</code> property at the start of the file:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+PROPERTY: header-args:python :session hello
|
||
</span></span><span style="display:flex;"><span>#+PROPERTY: header-args:python+ :async yes
|
||
</span></span></code></pre></div><p>When a kernel is initialized, an associated REPL buffer is also created with a name like <code>*jupyter-repl[python 3.9.2]-hello*</code>.</p>
|
||
<p>One advantage of emacs-jupyter over the standard Org source execution is that kernel requests for input are queried through the minibuffer. So, you can run a code like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python
|
||
</span></span><span style="display:flex;"><span>name = input(&#39;Name: &#39;)
|
||
</span></span><span style="display:flex;"><span>print(f&#39;Hello, {name}!&#39;)
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>: Hello, Pavel!
|
||
</span></span></code></pre></div><p>without any additional setup.</p>
|
||
<h2 id="code-output">Code output</h2>
|
||
<h3 id="images">Images</h3>
|
||
<p>Image output should work out of the box. Run <code>M-x org-toggle-inline-images</code> (<code>C-c C-x C-v</code>) after the execution to see the image inline.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python
|
||
</span></span><span style="display:flex;"><span>import matplotlib.pyplot as plt
|
||
</span></span><span style="display:flex;"><span>fig, ax = plt.subplots()
|
||
</span></span><span style="display:flex;"><span>ax.plot([1, 2, 3, 4], [1, 4, 2, 3])
|
||
</span></span><span style="display:flex;"><span>pass
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>[[file:./.ob-jupyter/86b3c5e1bbaee95d62610e1fb9c7e755bf165190.png]]
|
||
</span></span></code></pre></div><p>There is some room for improvement though. First, you can add the following hook if you don&rsquo;t want to press this awkward keybinding every time:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;org-babel-after-execute-hook</span> <span style="color:#19177c">&#39;org-redisplay-inline-images</span>)
|
||
</span></span></code></pre></div><p>Second, we may override the image save path like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python :file img/hello.png
|
||
</span></span><span style="display:flex;"><span>import matplotlib.pyplot as plt
|
||
</span></span><span style="display:flex;"><span>fig, ax = plt.subplots()
|
||
</span></span><span style="display:flex;"><span>ax.plot([1, 2, 3, 4], [1, 4, 2, 3])
|
||
</span></span><span style="display:flex;"><span>pass
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>[[file:img/hello.png]]
|
||
</span></span></code></pre></div><p>That can save you a <code>savefig</code> call if the image has to be used somewhere further.</p>
|
||
<p>Finally, by default, the image has a transparent background and a ridiculously small size. That can be fixed with some matplotlib settings:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">import</span> <span style="color:#00f;font-weight:bold">matplotlib</span> <span style="color:#008000;font-weight:bold">as</span> <span style="color:#00f;font-weight:bold">mpl</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>mpl<span style="color:#666">.</span>rcParams[<span style="color:#ba2121">&#39;figure.dpi&#39;</span>] <span style="color:#666">=</span> <span style="color:#666">200</span>
|
||
</span></span><span style="display:flex;"><span>mpl<span style="color:#666">.</span>rcParams[<span style="color:#ba2121">&#39;figure.facecolor&#39;</span>] <span style="color:#666">=</span> <span style="color:#ba2121">&#39;1&#39;</span>
|
||
</span></span></code></pre></div><p>At the same time, we can set the image width to prevent images from becoming too large. I prefer to do it inside a <code>emacs-lisp</code> code block in the same org file:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq-local</span> <span style="color:#19177c">org-image-actual-width</span> <span style="color:#666">&#39;</span>(<span style="color:#666">1024</span>))
|
||
</span></span></code></pre></div><h3 id="basic-tables">Basic tables</h3>
|
||
<p>If you are evaluating something like pandas DataFrame, it will be outputted in the HTML format, wrapped in the <code>begin_export</code> block. To view the data in text format, you can set <code>:display plain</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python :display plain
|
||
</span></span><span style="display:flex;"><span>import pandas as pd
|
||
</span></span><span style="display:flex;"><span>pd.DataFrame({&#34;a&#34;: [1, 2], &#34;b&#34;: [3, 4]})
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>: a b
|
||
</span></span><span style="display:flex;"><span>: 0 1 3
|
||
</span></span><span style="display:flex;"><span>: 1 2 4
|
||
</span></span></code></pre></div><p>Another solution is to use something like the <a href="https://pypi.org/project/tabulate/">tabulate</a> package:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python
|
||
</span></span><span style="display:flex;"><span>import pandas as pd
|
||
</span></span><span style="display:flex;"><span>import tabulate
|
||
</span></span><span style="display:flex;"><span>df = pd.DataFrame({&#34;a&#34;: [1, 2], &#34;b&#34;: [3, 4]})
|
||
</span></span><span style="display:flex;"><span>print(tabulate.tabulate(df, headers=df.columns, tablefmt=&#34;orgtbl&#34;))
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>: | | a | b |
|
||
</span></span><span style="display:flex;"><span>: |----+-----+-----|
|
||
</span></span><span style="display:flex;"><span>: | 0 | 1 | 3 |
|
||
</span></span><span style="display:flex;"><span>: | 1 | 2 | 4 |
|
||
</span></span></code></pre></div><h3 id="html-and-other-rich-output">HTML &amp; other rich output</h3>
|
||
<p>Yet another solution is to use emacs-jupyter&rsquo;s option <code>:pandoc t</code>, which invokes pandoc to convert HTML, LaTeX, and Markdown to Org. Predictably, this is slower than the options above.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+begin_src python :pandoc t
|
||
</span></span><span style="display:flex;"><span>import pandas as pd
|
||
</span></span><span style="display:flex;"><span>df = pd.DataFrame({&#34;a&#34;: [1, 2], &#34;b&#34;: [3, 4]})
|
||
</span></span><span style="display:flex;"><span>df
|
||
</span></span><span style="display:flex;"><span>#+end_src
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>#+RESULTS:
|
||
</span></span><span style="display:flex;"><span>:RESULTS:
|
||
</span></span><span style="display:flex;"><span>| | a | b |
|
||
</span></span><span style="display:flex;"><span>|---+---+---|
|
||
</span></span><span style="display:flex;"><span>| 0 | 1 | 3 |
|
||
</span></span><span style="display:flex;"><span>| 1 | 2 | 4 |
|
||
</span></span><span style="display:flex;"><span>:END:
|
||
</span></span></code></pre></div><p>Also, every once in a while I have to view an actual, unconverted HTML in a browser, e.g. when using <a href="https://python-visualization.github.io/folium/">folium</a> or <a href="https://spacy.io/usage/visualizers">displaCy</a>.</p>
|
||
<p>To do that, I&rsquo;ve written a small function, which performs <code>xdg-open</code> on the HTML export block under the cursor:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/org-view-html-tmp-dir</span> <span style="color:#ba2121">&#34;/tmp/org-html-preview/&#34;</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">f</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/org-view-html</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">elem</span> (<span style="color:#19177c">org-element-at-point</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">temp-file-path</span> (<span style="color:#00f">concat</span> <span style="color:#19177c">my/org-view-html-tmp-dir</span> (<span style="color:#00f">number-to-string</span> (<span style="color:#00f">random</span> (<span style="color:#00f">expt</span> <span style="color:#666">2</span> <span style="color:#666">32</span>))) <span style="color:#ba2121">&#34;.html&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cond</span>
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#19177c">not</span> (<span style="color:#00f">eq</span> <span style="color:#19177c">&#39;export-block</span> (<span style="color:#00f">car</span> <span style="color:#19177c">elem</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">message</span> <span style="color:#ba2121">&#34;Not in an export block!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> ((<span style="color:#19177c">not</span> (<span style="color:#00f">string-equal</span> (<span style="color:#00f">plist-get</span> (<span style="color:#00f">car</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">elem</span>)) <span style="color:#008000">:type</span>) <span style="color:#ba2121">&#34;HTML&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">message</span> <span style="color:#ba2121">&#34;Export block is not HTML!&#34;</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#800">t</span> (<span style="color:#008000">progn</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">f-mkdir</span> <span style="color:#19177c">my/org-view-html-tmp-dir</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">f-write</span> (<span style="color:#00f">plist-get</span> (<span style="color:#00f">car</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">elem</span>)) <span style="color:#008000">:value</span>) <span style="color:#19177c">&#39;utf-8</span> <span style="color:#19177c">temp-file-path</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;org-html-preview&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;xdg-open&#34;</span> <span style="color:#19177c">temp-file-path</span>))))))
|
||
</span></span></code></pre></div><p><code>f.el</code> is used by a lot of packages, including the above-mentioned <code>conda.el</code>, so you probably already have it installed.</p>
|
||
<p>Put a cursor on the <code>begin_export html</code> block and run <code>M-x my/org-view-html</code>.</p>
|
||
<p>There also <a href="https://github.com/nnicandro/emacs-jupyter#building-the-widget-support-experimental">seems to be widgets support</a> in emacs-jupyter, but I wasn&rsquo;t able to make it work.</p>
|
||
<h3 id="dataframes">DataFrames</h3>
|
||
<p>Last but not least option I want to mention here is specifically about pandas&rsquo; DataFrames. There aren&rsquo;t many good options to view the full dataframe inside Emacs. One way I can think of is to save the dataframe in CSV and view it with <code>csv-mode</code>.</p>
|
||
<p>However, there are standalone packages to view dataframes. One I can point out is <a href="https://github.com/man-group/dtale">dtale</a>, which is a Flask + React app designed just for that purpose. It has a rather extensive list of features, including charting, basic statistical instruments, filters, etc. <a href="http://alphatechadmin.pythonanywhere.com/dtale/main/1">Here</a> is an online demo.</p>
|
||
<p>The problem here is that it&rsquo;s a browser app, which means it defies one of the purposes of using Org Mode in the first place. What&rsquo;s more, this application is a rather huge one with lots of dependencies, and they have to be installed in the same environment as your project.</p>
|
||
<p>So this approach has its pros and cons as well. Example usage is as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">import</span> <span style="color:#00f;font-weight:bold">dtale</span>
|
||
</span></span><span style="display:flex;"><span>d <span style="color:#666">=</span> dtale<span style="color:#666">.</span>show(df)
|
||
</span></span><span style="display:flex;"><span>d<span style="color:#666">.</span>open_browser() <span style="color:#408080;font-style:italic"># Or get an URL from d._url</span>
|
||
</span></span></code></pre></div><p>Another notable alternative is <a href="https://github.com/adamerose/pandasgui">PandasGUI</a>, which, as one can guess, is a GUI (PyQt5) application, although it uses QtWebEngine inside.</p>
|
||
<h2 id="remote-kernels">Remote kernels</h2>
|
||
<p>There are yet some problems in the current configuration.</p>
|
||
<ul>
|
||
<li>Input/output handling is far from perfect. For instance, (at least in my configuration) Emacs tends to get slow for log-like outputs, e.g. Keras with <code>verbose=2</code>. It may even hang if the output is one long line.</li>
|
||
<li><code>ipdb</code> behaves awkwardly if called from an <code>src</code> block, although it at least will let you type <code>quit</code>.</li>
|
||
<li>Whenever you close Emacs, kernels are stopped, so you&rsquo;d have to execute the code again on the next start.</li>
|
||
</ul>
|
||
<h3 id="using-a-remote-kernel">Using a &ldquo;remote&rdquo; kernel</h3>
|
||
<p>For the reasons above I sometimes prefer to use a standalone kernel. To start a Jupyter kernel, run the following command in the environment and path you need:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>jupyter kernel --kernel<span style="color:#666">=</span>python
|
||
</span></span></code></pre></div><p>After the kernel is launched, write the path to the connection file into the <code>:session</code> header and press <code>C-c C-c</code> to refresh the setup:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+PROPERTY: header-args:python :session /home/pavel/.local/share/jupyter/runtime/kernel-e770599c-2c98-429b-b9ec-4d1ddf5fc16c.json
|
||
</span></span></code></pre></div><p>Now python source blocks should be executed in the kernel.</p>
|
||
<p>To open a REPL, run <code>M-x jupyter-connect-repl</code> and select the given JSON. Or launch a standalone REPL like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>jupyter qtconsole --existing kernel-e770599c-2c98-429b-b9ec-4d1ddf5fc16c.json
|
||
</span></span></code></pre></div><p>Executing a piece of code in the REPL allows proper debugging, for instance with <code>%pdb</code> magic. Also, Jupyter QtConsole generally handles large outputs better and even allows certain kinds of rich output in the REPL.</p>
|
||
<h3 id="some-automation">Some automation</h3>
|
||
<p>Now, I wouldn&rsquo;t use Emacs if it wasn&rsquo;t possible to automate at least some of the listed steps. So here are the functions I&rsquo;ve written for that.</p>
|
||
<p>First, we need to get open ports on the system:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/get-open-ports</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapcar</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#00f">#&#39;string-to-number</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">split-string</span> (<span style="color:#19177c">shell-command-to-string</span> <span style="color:#ba2121">&#34;ss -tulpnH | awk &#39;{print $5}&#39; | sed -e &#39;s/.*://&#39;&#34;</span>) <span style="color:#ba2121">&#34;\n&#34;</span>)))
|
||
</span></span></code></pre></div><p>Then, list the available kernel JSONs:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/jupyter-runtime-folder</span> (<span style="color:#00f">expand-file-name</span> <span style="color:#ba2121">&#34;~/.local/share/jupyter/runtime&#34;</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/list-jupyter-kernel-files</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapcar</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">file</span>) (<span style="color:#00f">cons</span> (<span style="color:#00f">car</span> <span style="color:#19177c">file</span>) (<span style="color:#00f">cdr</span> (<span style="color:#00f">assq</span> <span style="color:#19177c">&#39;shell_port</span> (<span style="color:#19177c">json-read-file</span> (<span style="color:#00f">car</span> <span style="color:#19177c">file</span>))))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">sort</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">directory-files-and-attributes</span> <span style="color:#19177c">my/jupyter-runtime-folder</span> <span style="color:#800">t</span> <span style="color:#ba2121">&#34;.*kernel.*json$&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">x</span> <span style="color:#19177c">y</span>) (<span style="color:#19177c">not</span> (<span style="color:#00f">time-less-p</span> (<span style="color:#00f">nth</span> <span style="color:#666">6</span> <span style="color:#19177c">x</span>) (<span style="color:#00f">nth</span> <span style="color:#666">6</span> <span style="color:#19177c">y</span>)))))))
|
||
</span></span></code></pre></div><p>And query the user for a running kernel:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/select-jupyter-kernel</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">ports</span> (<span style="color:#19177c">my/get-open-ports</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">files</span> (<span style="color:#19177c">my/list-jupyter-kernel-files</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">completing-read</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Jupyter kernels: &#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">file</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">member</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">file</span>) <span style="color:#19177c">ports</span>))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">files</span>))))
|
||
</span></span></code></pre></div><p>After which we can use the <code>my/select-jupyter-kernel</code> function however we want:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/insert-jupyter-kernel</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Insert a path to an active Jupyter kernel into the buffer&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span> (<span style="color:#19177c">my/select-jupyter-kernel</span>)))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/jupyter-connect-repl</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Open emacs-jupyter REPL, connected to a Jupyter kernel&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">jupyter-connect-repl</span> (<span style="color:#19177c">my/select-jupyter-kernel</span>) <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#800">t</span>))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/jupyter-qtconsole</span> ()
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Open Jupyter QtConsole, connected to a Jupyter kernel&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">start-process</span> <span style="color:#ba2121">&#34;jupyter-qtconsole&#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;setsid&#34;</span> <span style="color:#ba2121">&#34;jupyter&#34;</span> <span style="color:#ba2121">&#34;qtconsole&#34;</span> <span style="color:#ba2121">&#34;--existing&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">file-name-nondirectory</span> (<span style="color:#19177c">my/select-jupyter-kernel</span>))))
|
||
</span></span></code></pre></div><p>The first function, which simply inserts the path to the kernel, is meant to be used on the <code>:session</code> header. One can go even further and locate the header automatically, but that&rsquo;s an idea for next time.</p>
|
||
<p>The second one opens a REPL provided by emacs-jupyter. The <code>t</code> argument is necessary to pop up the REPL immediately.</p>
|
||
<p>The last one launches Jupyter QtConsole. <code>setsid</code> is required to run the program in a new session, so it won&rsquo;t close together with Emacs.</p>
|
||
<h3 id="cleaning-up">Cleaning up</h3>
|
||
<p>I&rsquo;ve also noticed that there are JSON files left in the runtime folder whenever the kernel isn&rsquo;t stopped correctly. So here is a cleanup function.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/jupyter-cleanup-kernels</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">ports</span> (<span style="color:#19177c">my/get-open-ports</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">files</span> (<span style="color:#19177c">my/list-jupyter-kernel-files</span>))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">to-delete</span> (<span style="color:#19177c">seq-filter</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">file</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#00f">member</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">file</span>) <span style="color:#19177c">ports</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">files</span>)))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#008000">and</span> (<span style="color:#19177c">length&gt;</span> <span style="color:#19177c">to-delete</span> <span style="color:#666">0</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">y-or-n-p</span> (<span style="color:#00f">format</span> <span style="color:#ba2121">&#34;Delete %d files?&#34;</span> (<span style="color:#00f">length</span> <span style="color:#19177c">to-delete</span>))))
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">dolist</span> (<span style="color:#19177c">file</span> <span style="color:#19177c">to-delete</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#00f">delete-file</span> (<span style="color:#00f">car</span> <span style="color:#19177c">file</span>)))))
|
||
</span></span></code></pre></div><h2 id="export">Export</h2>
|
||
<p>An uncountable number of articles have been written already on the subject of Org Mode export, so I will just cover my particular setup.</p>
|
||
<h3 id="html">HTML</h3>
|
||
<p>Export to a standalone HTML is an easy way to share the code with someone who doesn&rsquo;t use Emacs, just remember that HTML may not be the only file you&rsquo;d have to share if you have images in the document. Although you may use something like <a href="https://github.com/BitLooter/htmlark">htmlark</a> later to get a proper self-contained HTML.</p>
|
||
<p>To do the export, run <code>M-x org-html-export-to-html</code>. It should work out of the box, however, we can improve the output a bit.</p>
|
||
<p>First, we can add a custom CSS to the file. I like this one:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+HTML_HEAD: &lt;link rel=&#34;stylesheet&#34; type=&#34;text/css&#34; href=&#34;https://gongzhitaao.org/orgcss/org.css&#34;/&gt;
|
||
</span></span></code></pre></div><p>To get a syntax highlighting, we need the <code>htmlize</code> package:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">htmlize</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:after</span> <span style="color:#19177c">ox</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">org-html-htmlize-output-type</span> <span style="color:#19177c">&#39;css</span>))
|
||
</span></span></code></pre></div><p>If you use the <a href="https://github.com/Fanael/rainbow-delimiters">rainbow-delimeters</a> package, as I do, default colors for delimiters may not look good with the light theme. To fix such issues, put an HTML snippet like this in a <code>begin_export html</code> block or a CSS file:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#008000;font-weight:bold">style</span> <span style="color:#7d9029">type</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;text/css&#34;</span>&gt;
|
||
</span></span><span style="display:flex;"><span>.<span style="color:#00f;font-weight:bold">org-rainbow-delimiters-depth-1</span><span style="color:#666">,</span> .<span style="color:#00f;font-weight:bold">org-rainbow-delimiters-depth-2</span><span style="color:#666">,</span> .<span style="color:#00f;font-weight:bold">org-rainbow-delimiters-depth-3</span><span style="color:#666">,</span> .<span style="color:#00f;font-weight:bold">org-rainbow-delimiters-depth-4</span> {
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000;font-weight:bold">color</span>: <span style="color:#008000;font-weight:bold">black</span>
|
||
</span></span><span style="display:flex;"><span>}
|
||
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#008000;font-weight:bold">style</span>&gt;
|
||
</span></span></code></pre></div><h3 id="latex-pdf">LaTeX -&gt; pdf</h3>
|
||
<p>Even though I use LaTeX quite extensively, I don&rsquo;t like to add another layer of complexity here and 98% of the time write plain <code>.tex</code> files. LaTeX by itself provides many good options whenever you need to write a document together with some data or source code, contrary to &ldquo;traditional&rdquo; text processors.</p>
|
||
<p>Nevertheless, I want to get at least a tolerable pdf from Org, so here is a piece of my config with some inline comments.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/setup-org-latex</span> ()
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">org-latex-compiler</span> <span style="color:#ba2121">&#34;xelatex&#34;</span>) <span style="color:#408080;font-style:italic">;; Probably not necessary</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">org-latex-pdf-process</span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;latexmk -outdir=%o %f&#34;</span>)) <span style="color:#408080;font-style:italic">;; Use latexmk</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">org-latex-listings</span> <span style="color:#19177c">&#39;minted</span>) <span style="color:#408080;font-style:italic">;; Use minted to highlight source code</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">org-latex-minted-options</span> <span style="color:#408080;font-style:italic">;; Some minted options I like</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>((<span style="color:#ba2121">&#34;breaklines&#34;</span> <span style="color:#ba2121">&#34;true&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;tabsize&#34;</span> <span style="color:#ba2121">&#34;4&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;autogobble&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;linenos&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;numbersep&#34;</span> <span style="color:#ba2121">&#34;0.5cm&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;xleftmargin&#34;</span> <span style="color:#ba2121">&#34;1cm&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;frame&#34;</span> <span style="color:#ba2121">&#34;single&#34;</span>)))
|
||
</span></span><span style="display:flex;"><span> <span style="color:#408080;font-style:italic">;; Use extarticle without the default packages</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">add-to-list</span> <span style="color:#19177c">&#39;org-latex-classes</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>(<span style="color:#ba2121">&#34;org-plain-extarticle&#34;</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;\\documentclass{extarticle}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">[NO-DEFAULT-PACKAGES]
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">[PACKAGES]
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">[EXTRA]&#34;</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;\\section{%s}&#34;</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;\\section*{%s}&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;\\subsection{%s}&#34;</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;\\subsection*{%s}&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;\\subsubsection{%s}&#34;</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;\\subsubsection*{%s}&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;\\paragraph{%s}&#34;</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;\\paragraph*{%s}&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#ba2121">&#34;\\subparagraph{%s}&#34;</span> <span style="color:#666">.</span> <span style="color:#ba2121">&#34;\\subparagraph*{%s}&#34;</span>))))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic">;; Make sure to eval the function when org-latex-classes list already exists</span>
|
||
</span></span><span style="display:flex;"><span>(<span style="color:#008000">with-eval-after-load</span> <span style="color:#19177c">&#39;ox-latex</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/setup-org-latex</span>))
|
||
</span></span></code></pre></div><p>In the document itself, add the following headers:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+LATEX_CLASS: org-plain-extarticle
|
||
</span></span><span style="display:flex;"><span>#+LATEX_CLASS_OPTIONS: [a4paper, 14pt]
|
||
</span></span></code></pre></div><p>14pt size is required by certain state standards of ours for some reason.</p>
|
||
<p>After which you can put whatever you want in the preamble with <code>LATEX_HEADER</code>. My workflow with LaTeX is to write a bunch of <code>.sty</code> files beforehand and import the necessary ones in the preamble. <a href="https://github.com/SqrtMinusOne/LaTeX_templates">Here</a> is the repo with these files, although quite predictably, it&rsquo;s a mess. At any rate, I have to write something like the following in the target Org file:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>#+LATEX_HEADER: \usepackage{styles/generalPreamble}
|
||
</span></span><span style="display:flex;"><span>#+LATEX_HEADER: \usepackage{styles/reportFormat}
|
||
</span></span><span style="display:flex;"><span>#+LATEX_HEADER: \usepackage{styles/mintedSourceCode}
|
||
</span></span><span style="display:flex;"><span>#+LATEX_HEADER: \usepackage{styles/russianLocale}
|
||
</span></span></code></pre></div><p><code>M-x org-latex-export-to-latex</code> should export the document to the .tex file. As an alternative, run <code>M-x org-export-dispatch</code> (by default should be on <code>C-c C-e</code>) an pick the required option there.</p>
|
||
<h3 id="ipynb">ipynb</h3>
|
||
<p>One last export backend I want to mention is <a href="https://github.com/jkitchin/ox-ipynb">ox-ipynb</a>, which allows exporting Org documents to Jupyter notebooks. Sometimes it works, sometimes it doesn&rsquo;t.</p>
|
||
<p>Also, the package isn&rsquo;t on MELPA, so you have to install it from the repo directly.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">ox-ipynb</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> (<span style="color:#008000">:host</span> <span style="color:#19177c">github</span> <span style="color:#008000">:repo</span> <span style="color:#ba2121">&#34;jkitchin/ox-ipynb&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:after</span> <span style="color:#19177c">ox</span>)
|
||
</span></span></code></pre></div><p>To (try to) do export, run <code>M-x ox-ipynb-export-org-file-ipynb-file</code>.</p>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>Multiple Gmail accounts & labels with Emacs</title>
|
||
<link>https://sqrtminusone.xyz/posts/2021-02-27-gmail/</link>
|
||
<pubDate>Sat, 27 Feb 2021 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/2021-02-27-gmail/</guid>
|
||
<content type="html">
|
||
<h2 id="intro">Intro</h2>
|
||
<p>For quite some time, e-mail seemed like an anomaly in my workflow. I am a long time Gmail user, and my decade-old account has a somewhat formidable quantity of labels and filters. My messages are often assigned multiple labels, and I also like to keep only a bunch of messages in the inbox.</p>
|
||
<p>Although, in my opinion, Gmail web UI was and still is leagues ahead of many of its competitors and even allows keyboard-centric workflow, it&rsquo;s awkward to use with a keyboard-driven browser, and for no money on Earth I would enable browser notifications.</p>
|
||
<p>Any classical IMAP/SMTP client is hard to use in my case, because a message with multiple labels is copied to IMAP folders for each of the label plus the inbox folder, and the copies look like different messages from the client-side. For example, a message can be read in one label and unread in another.</p>
|
||
<p>For a few years, my solution was <a href="https://getmailspring.com/">Mailspring</a>, which provides first-class support for labels. However, it has a feature to deploy <a href="https://www.bbc.com/news/technology-56071437">spy pixels</a> on emails (and offers no protection from them, obviously), the client is Electron-based with a mouse-driven interface, and the sync engine was closed-source at the time.</p>
|
||
<p>So, I found an alternative in Emacs+notmuch+lieer and ditched one more proprietary app (the last big one I can&rsquo;t let go of is DataGrip).</p>
|
||
<figure><img src="https://sqrtminusone.xyz/images/gmail/main.png"/>
|
||
</figure>
|
||
|
||
<figure><img src="https://sqrtminusone.xyz/images/gmail/mail.png"/>
|
||
</figure>
|
||
|
||
<p>Notmuch&rsquo;s tags are just as advanced as Gmail&rsquo;s labels, so I have basically the same mail structure accessible from Emacs, Gmail Android client and even the web UI when I don&rsquo;t have access to the first two.</p>
|
||
<p>Also, I think the setup I describe here is pretty straightforward and less complex than many I encountered, but my impression is not the most reliable source of such knowledge.</p>
|
||
<p>In any case, what follows is a description of my current workflow with instructions of varying levels of precision of how to get there.</p>
|
||
<h2 id="setting-up">Setting up</h2>
|
||
<h3 id="gmail">Gmail</h3>
|
||
<p>Before we start, some setup is required for the Gmail account.</p>
|
||
<p>First, as there is no way to enable SMTP without IMAP on Gmail, you have to set &ldquo;Enable IMAP&rdquo; in the &ldquo;Forwarding and POP/IMAP&rdquo; tab in the settings. If you use two-factor auth, generate an <a href="https://support.google.com/accounts/answer/185833?hl=en">app password</a>.</p>
|
||
<p>Also, make sure your labels do not contain whitespaces because if they do, you will have to type them in quotes all the time.</p>
|
||
<h3 id="lieer">lieer</h3>
|
||
<p><a href="https://github.com/gauteh/lieer">lieer</a> (formerly gmailieer) is a program that uses Gmail API to download email and synchronize Gmail labels with notmuch tags. Because of its usage of Gmail API instead of IMAP, there are no problems with duplicating emails in different labels, etc.</p>
|
||
<p>As I need to use multiple versions of Python &amp; Node.js for other reasons, I manage my installations of them with <a href="https://anaconda.org">Anaconda</a> (Miniconda, to be precise). You may instead use <a href="https://docs.python.org/3/library/venv.html">venv</a> or even the system-wide installation of Python and omit the <code>conda</code> clauses, but in my experience Anaconda makes life easier in that regard.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Create an environment with the name &#34;mail&#34;</span>
|
||
</span></span><span style="display:flex;"><span>conda create --name mail
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Activate the environment</span>
|
||
</span></span><span style="display:flex;"><span>conda activate mail
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Install Python</span>
|
||
</span></span><span style="display:flex;"><span>conda install python
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Download and install lieer</span>
|
||
</span></span><span style="display:flex;"><span>git clone https://github.com/gauteh/lieer.git
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">cd</span> lieer
|
||
</span></span><span style="display:flex;"><span>pip install .
|
||
</span></span></code></pre></div><p>After which we may check if the <code>gmi</code> executable is available:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>which gmi
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>/home/pavel/Programs/miniconda3/envs/mail/bin/gmi
|
||
</span></span></code></pre></div><h3 id="notmuch">Notmuch</h3>
|
||
<p><a href="https://notmuchmail.org/">Notmuch</a> is present in most of the package repositories, so you can install it with your package manager, which is <code>pacman</code> in my case.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo pacman -S notmuch
|
||
</span></span></code></pre></div><p>After the installation, run <code>notmuch setup</code>. That will inquire the parameters and create the <code>.notmuch-config</code> file with the answers.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>Your full name <span style="color:#666">[</span>Pavel<span style="color:#666">]</span>: Pavel Korytov
|
||
</span></span><span style="display:flex;"><span>Your primary email address <span style="color:#666">[</span>pavel@pdsk.<span style="color:#666">(</span>none<span style="color:#666">)]</span>: thexcloud@gmail.com
|
||
</span></span><span style="display:flex;"><span>Additional email address <span style="color:#666">[</span>Press <span style="color:#ba2121">&#39;Enter&#39;</span> <span style="color:#008000;font-weight:bold">if</span> none<span style="color:#666">]</span>:
|
||
</span></span><span style="display:flex;"><span>Top-level directory of your email archive <span style="color:#666">[</span>/home/pavel/mail<span style="color:#666">]</span>: /home/pavel/Mail
|
||
</span></span><span style="display:flex;"><span>Tags to apply to all new messages <span style="color:#666">(</span>separated by spaces<span style="color:#666">)</span> <span style="color:#666">[</span>unread inbox<span style="color:#666">]</span>: new
|
||
</span></span><span style="display:flex;"><span>Tags to exclude when searching messages <span style="color:#666">(</span>separated by spaces<span style="color:#666">)</span> <span style="color:#666">[</span>deleted spam<span style="color:#666">]</span>:
|
||
</span></span></code></pre></div><p>It is important to set the <code>new</code> tag for the new messages instead of the default <code>unread</code> and <code>inbox</code>.</p>
|
||
<p>Next, add the rule to ignore JSON files to the <code>[new]</code> section of the <code>.notmuch-config</code> file, so it would look like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#666">[</span>new<span style="color:#666">]</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">tags</span><span style="color:#666">=</span>new
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">ignore</span><span style="color:#666">=</span>/.*<span style="color:#666">[</span>.<span style="color:#666">](</span>json|lock|bak<span style="color:#666">)</span>$/
|
||
</span></span></code></pre></div><p>That is needed to ignore the lieer config files. Although, as I have noticed, notmuch is generally pretty good at detecting wrong files in its directories, an explicit ignore rule won&rsquo;t hurt.</p>
|
||
<p>Now, create the mail directory and run the <a href="https://notmuchmail.org/manpages/notmuch-new-1/">notmuch new</a> command. As notmuch has probably already noticed you, it uses the <a href="https://en.wikipedia.org/wiki/Maildir">maildir</a> format, which basically means that one message is stored in one file.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># The same directory mentioned in the 4th question</span>
|
||
</span></span><span style="display:flex;"><span>mkdir ~/Mail
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Initialize notmuch</span>
|
||
</span></span><span style="display:flex;"><span>notmuch new
|
||
</span></span></code></pre></div><h3 id="add-an-account">Add an account</h3>
|
||
<p>After that, we can create a directory for a mail account and initialize lieer.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#008000">cd</span> ~/Mail
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Use whatever name you want</span>
|
||
</span></span><span style="display:flex;"><span>mkdir thexcloud
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">cd</span> thexcloud
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Intialize lieer</span>
|
||
</span></span><span style="display:flex;"><span>gmi init thexcloud@gmail.com
|
||
</span></span></code></pre></div><p>Running <code>gmi init</code> will run an OAuth authentication to your Gmail account. The credentials will be stored in <code>.credentials.gmailieer.json</code> file, so make sure not to expose it somewhere.</p>
|
||
<p>We also can add a few settings for lieer, which will make life easier. First, dots seem to be less awkward to type than slashes for the nested tags:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gmi <span style="color:#008000">set</span> --replace-slash-with-dot
|
||
</span></span></code></pre></div><p>Then, we don&rsquo;t want the <code>new</code> tag to be pushed back to Gmail</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gmi <span style="color:#008000">set</span> --ignore-tags-local new
|
||
</span></span></code></pre></div><p>Now we can finally download the mail directory. To initiate the download, run</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gmi sync
|
||
</span></span></code></pre></div><p>The first download can easily take several hours, depending on the size of your email and the speed of your internet connection, but subsequent runs will be much faster.</p>
|
||
<p>The last thing to do here is to add the <code>gmi sync</code> command to notmuch&rsquo;s <a href="https://notmuchmail.org/manpages/notmuch-hooks-5/">pre-new hook</a>, so that the email will be synchronized on the <code>notmuch new</code> command.</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Create the hooks folder</span>
|
||
</span></span><span style="display:flex;"><span>mkdir -p ~/Mail/.notmuch/hooks
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Create the file</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">cd</span> ~/Mail/.notmuch/hooks
|
||
</span></span><span style="display:flex;"><span>cat &gt; pre-new <span style="color:#ba2121">&lt;&lt;EOF
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">#!/bin/bash
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">eval &#34;$(conda shell.bash hook)&#34;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">conda activate mail
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">(cd /home/pavel/Mail/thexcloud/ &amp;&amp; gmi sync)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">EOF</span>
|
||
</span></span><span style="display:flex;"><span>chmod +x pre-new
|
||
</span></span></code></pre></div><p>Side note: as a hook for <code>conda</code> tends to be rather slow, I run the <code>gmi</code> command with system-wide Python as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#bc7a00">#!/bin/bash
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#bc7a00"></span><span style="color:#19177c">GMI</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;/home/pavel/Programs/miniconda3/envs/mail/bin/gmi&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#666">(</span><span style="color:#008000">cd</span> /home/pavel/Mail/thexcloud/ <span style="color:#666">&amp;&amp;</span> <span style="color:#19177c">$GMI</span> sync<span style="color:#666">)</span>
|
||
</span></span></code></pre></div><p>Which doesn&rsquo;t seem to cause any particular trouble in that case.</p>
|
||
<h3 id="emacs">Emacs</h3>
|
||
<p>There are plenty of different <a href="https://notmuchmail.org/frontends/">frontends</a> for notmuch (even GUI apps), but the one I&rsquo;m sticking with the Emacs.</p>
|
||
<p>Configuration for Emacs is pretty straightforward, but you probably want to use the notmuch package which came with the system package, because otherwise, you may end up with different versions of frontend and backend.</p>
|
||
<p>That&rsquo;s how it can be done with <code>use-package</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">notmuch</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:ensure</span> <span style="color:#800">nil</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:commands</span> (<span style="color:#19177c">notmuch</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">add-hook</span> <span style="color:#19177c">&#39;notmuch-hello-mode-hook</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> () (<span style="color:#19177c">display-line-numbers-mode</span> <span style="color:#666">0</span>))))
|
||
</span></span></code></pre></div><p>The only notable observation here is that <code>display-line-numbers-mode</code> seems to break formatting of the <code>notmuch-hello</code> page.</p>
|
||
<p>If you use evil-mode, you also should enable the <a href="https://github.com/emacs-evil/evil-collection/blob/master/modes/notmuch/evil-collection-notmuch.el">evil-collection mode for notmuch</a>.</p>
|
||
<p>Now run <code>M-x notmuch</code> and the <code>notmuch-hello</code> page should appear. Running <code>notmuch-poll-and-refresh-this-buffer</code> (<code>gR</code> with evil bindings) will run the <code>notmuch new</code> command and refresh the buffer. All the syncronized messages should be present.</p>
|
||
<p>I should note that <a href="https://notmuchmail.org/notmuch-emacs/">notmuch frontend for Emacs</a> is the most user-friendly Emacs app I have seen so far. UI, commands and keybindings are self-descriptive, all the options can be configured with the build-in <code>customize</code> interface. It may be useful to look through <a href="https://notmuchmail.org/emacstips/">emacs tips</a> at the official site and <a href="https://notmuchmail.org/manpages/">notmuch man pages</a>, in particular <a href="https://notmuchmail.org/manpages/notmuch-search-terms-7/">syntax for notmuch queries</a>.</p>
|
||
<h3 id="reading-mail">Reading mail</h3>
|
||
<p><code>notmuch-search-show-thread</code> (<code>RET</code>) opens the thread under the cursor.</p>
|
||
<p><code>notmuch-show-view-part</code> (<code>. v</code> with evil) opens an attachment with associations defined in <a href="https://linux.die.net/man/4/mailcap">.mailcap</a> file. Mine looks like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>audio/*; mpc add %s
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>image/*; feh %s
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>application/msword; /usr/bin/xdg-open %s
|
||
</span></span><span style="display:flex;"><span>application/pdf; zathura %s
|
||
</span></span><span style="display:flex;"><span>application/postscript ; zathura %s
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>text/html; /usr/bin/xdg-open %s
|
||
</span></span></code></pre></div><p>Here watch out for the last line, default version of which may be set as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>text/html; /usr/bin/xdg-open %s ; copiousoutput
|
||
</span></span></code></pre></div><p>Which causes a temporary file to be deleted before it could be opened because recent versions of <code>xdg-open</code> do not block the input.</p>
|
||
<p>As expected, Emacs mail reader does not trigger any <a href="https://www.emailprivacytester.com/">spy pixels or other tracking contents of email</a> (not any I know of, at least). However, opening an HTML email in a browser will even run embedded JavaScript. Therefore, <strong>in no case open emails you do not trust with <code>xdg-open</code></strong>. Even if you use NoScript, the browser will still load all the CSS, videos and even iframes, which can be used to track you.</p>
|
||
<p>Even Gmail web UI is preferable to view the message in a browser, because the former blocks most of the malicious stuff and does not seem to leak your IP to the sender, for what it&rsquo;s worth.</p>
|
||
<h3 id="sending-mail">Sending mail</h3>
|
||
<p>To start composing a message, run <code>notmuch-mua-new-mail</code> (<code>C</code> with evil bindings).</p>
|
||
<p>After doing so, <code>C-c C-c</code> will run <code>notmuch-mua-send-and-exit</code>, which will invoke the function stated in the <code>message-send-mail-function</code> variable. The default value of the variable is <code>sendmail-query-once</code>, which will inquire the parameters and save them as custom variables.</p>
|
||
<p>If SMTP is used, <code>send-mail-function</code> will be set to the one from the built-it <a href="https://www.emacswiki.org/emacs/SendingMail">smtpmail</a> package. SMTP parameters for Gmail are listed <a href="https://support.google.com/mail/answer/7126229?hl=en">here</a>.</p>
|
||
<p>Authorization parameters will be saved to your <a href="https://www.emacswiki.org/emacs/GnusAuthinfo">authinfo</a> file. If you didn&rsquo;t have one, the plaintext <code>.authinfo</code> will be created, so it&rsquo;s reasonable to encrypt it:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#008000">cd</span> ~
|
||
</span></span><span style="display:flex;"><span>gpg -o .authinfo.gpg -c --cipher-algo AES256 .authinfo
|
||
</span></span></code></pre></div><p>However, if you plan to use multiple accounts with different SMTP servers, it makes more sense to use something like <a href="https://marlam.de/msmtp/msmtp.html">MSMTP</a> to manage multiple accounts. Here are a couple of examples (<a href="https://www.reddit.com/r/emacs/comments/9piml5/a_few_quick_emacsnotmuch_questions/e83zcck?utm_source=share&amp;utm_medium=web2x&amp;context=3">1</a>, <a href="https://www.reddit.com/r/emacs/comments/9piml5/a_few_quick_emacsnotmuch_questions/e84otah?utm_source=share&amp;utm_medium=web2x&amp;context=3">2</a>) how to do that.</p>
|
||
<p>Another alternative for Gmail is to use <a href="https://github.com/gauteh/lieer/wiki/GNU-Emacs-and-Lieer">lieer as sendmail program</a>. That may make sense if you don&rsquo;t want to enable IMAP and SMTP on your account.</p>
|
||
<p>There are also <a href="https://notmuchmail.org/emacstips/#index13h2">a bunch of ways</a> to set up address completion if the built-in completion based on notmuch database does not suffice.</p>
|
||
<p>I also use <a href="https://github.com/mhayashi1120/Emacs-langtool">LanguageTool for Emacs</a> to do a spell checking of important emails (integrations like that really make Emacs shine). For some reason, developers don&rsquo;t give a link to download the server on the frontpage, so <a href="https://dev.languagetool.org/http-server">here it is</a>. And here is the relevant part of my Emacs config:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">use-package</span> <span style="color:#19177c">langtool</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:straight</span> <span style="color:#800">t</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:commands</span> (<span style="color:#19177c">langtool-check</span>)
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">:config</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">langtool-language-tool-server-jar</span> <span style="color:#ba2121">&#34;/home/pavel/Programs/LanguageTool-5.1/languagetool-server.jar&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">langtool-mother-tongue</span> <span style="color:#ba2121">&#34;ru&#34;</span>))
|
||
</span></span></code></pre></div><p>As a last note here, to set up a signature create the <code>.signature</code> file in the <code>$HOME</code> directory. If you need more complex logic here, for instance, different signatures for different accounts, you can put an arbitrary expression to the <code>mail-signature</code> variable or apply <a href="https://notmuchmail.org/emacstips/#index16h2">this gnus-alias tip</a>.</p>
|
||
<h2 id="another-account">Another account</h2>
|
||
<h3 id="adding-an-account">Adding an account</h3>
|
||
<p>Now we can send and receive mail from one account. Adding another account is also pretty easy.</p>
|
||
<p>If another account is Gmail, the process starts the same as before:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Create a directory</span>
|
||
</span></span><span style="display:flex;"><span>mkdir -p ~/Mail/progin6304
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">cd</span> ~/Mail/progin6304
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># OAuth</span>
|
||
</span></span><span style="display:flex;"><span>gmi init progin6304@gmail.com
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Settings</span>
|
||
</span></span><span style="display:flex;"><span>gmi <span style="color:#008000">set</span> --replace-slash-with-dot
|
||
</span></span></code></pre></div><p>However, before running <code>gmi sync</code> for the second account, we want to make sure that we can distinguish the message from different accounts. To do that, I add the <code>main</code> for the main account and <code>progin</code> for the second account. We also don&rsquo;t want these labels to be pushed:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#008000">cd</span> ~/Mail/thexcloud
|
||
</span></span><span style="display:flex;"><span>gmi <span style="color:#008000">set</span> --ignore-tags-local new,mail,progin
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">cd</span> ~/Mail/progin6304
|
||
</span></span><span style="display:flex;"><span>gmi <span style="color:#008000">set</span> --ignore-tags-local new,mail,progin
|
||
</span></span></code></pre></div><p>Now we can use notmuch&rsquo;s <code>post-new</code> hook to tag the messages based on their folder as follows:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#008000">cd</span> ~/Mail/.notmuch/hooks
|
||
</span></span><span style="display:flex;"><span>cat &gt; post-new <span style="color:#ba2121">&lt;&lt;EOF
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">#!/bin/bash
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">notmuch tag +main &#34;path:thexcloud/** AND tag:new&#34;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">notmuch tag +progin &#34;path:progin6304/** AND tag:new&#34;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">notmuch tag -new &#34;tag:new&#34;
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">EOF</span>
|
||
</span></span><span style="display:flex;"><span>chmod +x post-new
|
||
</span></span></code></pre></div><p>Now it finally makes sense why we wanted to use the <code>new</code> tag in the first place. In principle, any kind of tagging logic can be applied here, but for the reasons I stated earlier, I prefer to set up filters in the Gmail web interface.</p>
|
||
<p>The last thing to do is to modify the <code>pre-new</code> hook:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#bc7a00">#!/bin/bash
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#bc7a00"></span><span style="color:#19177c">GMI</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;/home/pavel/Programs/miniconda3/envs/mail/bin/gmi&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#666">(</span><span style="color:#008000">cd</span> /home/pavel/Mail/thexcloud/ <span style="color:#666">&amp;&amp;</span> <span style="color:#19177c">$GMI</span> sync<span style="color:#666">)</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#666">(</span><span style="color:#008000">cd</span> /home/pavel/Mail/progin6304/ <span style="color:#666">&amp;&amp;</span> <span style="color:#19177c">$GMI</span> sync<span style="color:#666">)</span>
|
||
</span></span></code></pre></div><p>After which we can finally tag the existing messages and download ones from the new account</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>notmuch tag +main <span style="color:#ba2121">&#34;path:thexcloud/**&#34;</span>
|
||
</span></span><span style="display:flex;"><span>notmuch new
|
||
</span></span></code></pre></div><p>The obvious problem, however, is that the messages are fetched sequentially, which is rather slow. A solution is to use something like <a href="http://www.gnu.org/software/parallel/">GNU Parallel</a>:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#bc7a00">#!/bin/bash
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#bc7a00"></span><span style="color:#19177c">GMI</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;/home/pavel/Programs/miniconda3/envs/mail/bin/gmi&#34;</span>
|
||
</span></span><span style="display:flex;"><span>parallel -j0 <span style="color:#ba2121">&#34;(cd /home/pavel/Mail/{}/ &amp;&amp; </span><span style="color:#19177c">$GMI</span><span style="color:#ba2121"> sync)&#34;</span> ::: thexcloud progin6304
|
||
</span></span></code></pre></div><p>I haven&rsquo;t encountered any trouble with that solution so far (and I don&rsquo;t see anything thread-unsafe in the lieer code), but I&rsquo;ll keep an eye on that.</p>
|
||
<p>In principle, it shouldn&rsquo;t be too hard to add a normal IMAP account as well with <a href="https://isync.sourceforge.io/mbsync.html">mbsync</a>, but I expect it would require something like iterating through the directory structure and assigning notmuch labels based on that. I&rsquo;ll probably try that some time in the future.</p>
|
||
<h3 id="emacs">Emacs</h3>
|
||
<p>With that done, I also want separate entries on the start page for each of the accounts. Doing that is easy enough, just modify the <code>notmuch-saved-searches</code> variable with <code>customize-group</code> or like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">notmuch-saved-searches</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#666">&#39;</span>((<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;inbox (main)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:inbox AND tag:main&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;unread (main)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:unread AND tag:main&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;sent (main)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:sent AND tag:main&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;all mail (main)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:main&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;inbox (progin)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:inbox AND tag:progin&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;unread (progin)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:unread AND tag:progin&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;sent (progin)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:sent AND tag:progin&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;all main (progin)&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:progin&#34;</span>)
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#008000">:name</span> <span style="color:#ba2121">&#34;drafts&#34;</span> <span style="color:#008000">:query</span> <span style="color:#ba2121">&#34;tag:draft&#34;</span>)))
|
||
</span></span></code></pre></div><h2 id="notification-for-new-messages">Notification for new messages</h2>
|
||
<p>Now, we can send and receive mail, but we also probably want notifications for new emails. To do that, I wrote a simple script:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#bc7a00">#!/bin/bash
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#bc7a00"></span><span style="color:#408080;font-style:italic"># To run notify-send from cron</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">export</span> <span style="color:#19177c">DISPLAY</span><span style="color:#666">=</span>:0
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># A file with last time of sync</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">CHECK_FILE</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;/home/pavel/Mail/.last_check&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">QUERY</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;tag:unread&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">ALL_QUERY</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;tag:unread&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># If the file exists, check also the new messages from the last sync</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">if</span> <span style="color:#666">[</span> -f <span style="color:#ba2121">&#34;</span><span style="color:#19177c">$CHECK_FILE</span><span style="color:#ba2121">&#34;</span> <span style="color:#666">]</span>; <span style="color:#008000;font-weight:bold">then</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">DATE</span><span style="color:#666">=</span><span style="color:#008000;font-weight:bold">$(</span>cat <span style="color:#ba2121">&#34;</span><span style="color:#19177c">$CHECK_FILE</span><span style="color:#ba2121">&#34;</span><span style="color:#008000;font-weight:bold">)</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">QUERY</span><span style="color:#666">=</span><span style="color:#ba2121">&#34;</span><span style="color:#19177c">$QUERY</span><span style="color:#ba2121"> and date:@</span><span style="color:#19177c">$DATE</span><span style="color:#ba2121">..&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">fi</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>notmuch new
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">NEW_UNREAD</span><span style="color:#666">=</span><span style="color:#008000;font-weight:bold">$(</span>notmuch count <span style="color:#ba2121">&#34;</span><span style="color:#19177c">$QUERY</span><span style="color:#ba2121">&#34;</span><span style="color:#008000;font-weight:bold">)</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#19177c">ALL_UNREAD</span><span style="color:#666">=</span><span style="color:#008000;font-weight:bold">$(</span>notmuch count <span style="color:#ba2121">&#34;</span><span style="color:#19177c">$ALL_QUERY</span><span style="color:#ba2121">&#34;</span><span style="color:#008000;font-weight:bold">)</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># I don&#39;t really care if there are unread messages for which I&#39;ve already seen a notification</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">if</span> <span style="color:#666">[</span> <span style="color:#19177c">$NEW_UNREAD</span> -gt <span style="color:#666">0</span> <span style="color:#666">]</span>; <span style="color:#008000;font-weight:bold">then</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">MAIN_UNREAD</span><span style="color:#666">=</span><span style="color:#008000;font-weight:bold">$(</span>notmuch count <span style="color:#ba2121">&#34;tag:unread AND tag:main&#34;</span><span style="color:#008000;font-weight:bold">)</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#19177c">PROGIN_UNREAD</span><span style="color:#666">=</span><span style="color:#008000;font-weight:bold">$(</span>notmuch count <span style="color:#ba2121">&#34;tag:unread AND tag:progin&#34;</span><span style="color:#008000;font-weight:bold">)</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#008000">read</span> -r -d <span style="color:#ba2121">&#39;&#39;</span> NOTIFICATION <span style="color:#ba2121">&lt;&lt;EOM
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">$NEW_UNREAD new messages
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">$MAIN_UNREAD thexcloud@gmail.com
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">$PROGIN_UNREAD progin6304@gmail.com
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">$ALL_UNREAD total
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">EOM</span>
|
||
</span></span><span style="display:flex;"><span> notify-send <span style="color:#ba2121">&#34;New Mail&#34;</span> <span style="color:#ba2121">&#34;</span><span style="color:#19177c">$NOTIFICATION</span><span style="color:#ba2121">&#34;</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000;font-weight:bold">fi</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#408080;font-style:italic"># Save sync timestamp</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#008000">echo</span> <span style="color:#ba2121">&#34;</span><span style="color:#008000;font-weight:bold">$(</span>date +%s<span style="color:#008000;font-weight:bold">)</span><span style="color:#ba2121">&#34;</span> &gt; <span style="color:#19177c">$CHECK_FILE</span>
|
||
</span></span></code></pre></div><p>The script is launched with cron every 5 minutes:</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>*/5 * * * * bash /home/pavel/bin/scripts/check-email
|
||
</span></span></code></pre></div><p>Here&rsquo;s how the notification looks like:
|
||
<img src="https://sqrtminusone.xyz/images/gmail/notification.png" alt=""></p>
|
||
<h2 id="caveats">Caveats</h2>
|
||
<ul>
|
||
<li><a href="https://github.com/gauteh/lieer#caveats">lieer</a> has an extensive list of caveats concerning Gmail API</li>
|
||
<li>Make sure that you understand the <a href="https://github.com/gauteh/lieer#changing-ignored-tags-and-translation-after-initial-sync">implications</a> of lieer&rsquo;s <code>--ignore-tags-locally</code> and <code>--ignore-tags-remote</code></li>
|
||
<li>If two of your accounts receive the same email, it will be stored as one email in notmuch, so tags from these accounts will be merged and pushed back on the next sync. To solve that, you can set tags from one account to be ignored on the rest of the accounts</li>
|
||
<li>A sent email is being downloaded again on the next sync. Not a great deal, but it is somewhat annoying to download recently sent attachments.</li>
|
||
</ul>
|
||
|
||
</content>
|
||
</item>
|
||
|
||
<item>
|
||
<title>Hello, world!</title>
|
||
<link>https://sqrtminusone.xyz/posts/hello-world/</link>
|
||
<pubDate>Mon, 01 Feb 2021 00:00:00 +0000</pubDate>
|
||
|
||
<guid>https://sqrtminusone.xyz/posts/hello-world/</guid>
|
||
<content type="html">
|
||
<h2 id="hello-world">Hello, world!</h2>
|
||
<p>Eventually, there will be something interesting here. Or not.</p>
|
||
<p>Regradless, I&rsquo;ll check if I can write some Python here</p>
|
||
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#008000">print</span>(<span style="color:#ba2121">&#34;Hello, world&#34;</span>)
|
||
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Hello, world
|
||
</span></span></code></pre></div>
|
||
</content>
|
||
</item>
|
||
|
||
</channel>
|
||
</rss>
|