<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Ben Potter</title>
    <subtitle>Personal website for Ben Potter: masters computer engineer focused on future robotic systems.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://benpot.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://benpot.com"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-09-28T00:00:00+00:00</updated>
    <id>https://benpot.com/atom.xml</id>
    <entry xml:lang="en">
        <title>Linux Email Setup</title>
        <published>2026-09-28T00:00:00+00:00</published>
        <updated>2026-09-28T00:00:00+00:00</updated>
        
        <author>
          <name>Ben Potter</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://benpot.com/blog/local-mail-client/"/>
        <id>https://benpot.com/blog/local-mail-client/</id>
        
        <content type="html" xml:base="https://benpot.com/blog/local-mail-client/">&lt;p&gt;The following is documentation (mostly for myself) about how I configure my local mail setup.
I want to sync my personal mail account hosted by Proton, to a terminal-first local interface.&lt;/p&gt;
&lt;h2 id=&quot;1-proton-mail-bridge&quot;&gt;1. Proton Mail Bridge&lt;/h2&gt;
&lt;p&gt;Proton stores all mail encrypted on the server.
In order to read it locally, we have to run &lt;code&gt;protonmail-bridge&lt;/code&gt; which sits in between the Proton server and the local client.
The bridge encrypts/decrypts mail as it is passed from/to the local client.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/ProtonMail/proton-bridge&quot;&gt;https://github.com/ProtonMail/proton-bridge&lt;/a&gt;
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://archlinux.org/packages/extra/x86_64/protonmail-bridge/&quot;&gt;https://archlinux.org/packages/extra/x86_64/protonmail-bridge/&lt;/a&gt;
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://proton.me/mail/bridge&quot;&gt;https://proton.me/mail/bridge&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;install-on-arch-linux&quot;&gt;Install on Arch Linux&lt;/h3&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo pacman -S protonmail-bridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The package requires a system &lt;em&gt;keychain&lt;/em&gt; to be installed.
In my case, there was no such keychain installed by Arch by default.
I liked the simple approach used by &lt;code&gt;pass&lt;/code&gt;, so that is what I opted for.
Proton says that other keyrings can be used if you have something else you prefer.
See &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://proton.me/support/bridge-cannot-access-keychain&quot;&gt;this support article&lt;/a&gt; for more info.&lt;/p&gt;
&lt;p&gt;First, we need to install &lt;code&gt;pass&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo pacman -S pass&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;setup-a-keyring&quot;&gt;Setup a keyring&lt;/h3&gt;
&lt;p&gt;In order to get &lt;code&gt;pass&lt;/code&gt; working, we need to initialize a password store.
From the &lt;code&gt;pass&lt;/code&gt; manual page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The init command must be run before other commands in order to initialize the password store with the correct gpg key id.
Passwords are encrypted using the gpg key set with init.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Initializing a password store requires an OpenPGP key created by GnuPG on Arch.
GnuPG (or &lt;code&gt;gpg&lt;/code&gt; on the command-line) should already be installed as a dependency of &lt;code&gt;pacman&lt;/code&gt;.
Running &lt;code&gt;gpg&lt;/code&gt; for the first time on my machine yields:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[I] ben@thinkpad-x1 ~&amp;gt; gpg --list-keys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;gpg: directory &amp;#39;/home/ben/.gnupg&amp;#39; created&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;gpg: /home/ben/.gnupg/trustdb.gpg: trustdb created&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think this implies I have no keys setup yet.
To &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://wiki.archlinux.org/title/GnuPG#Create_a_key_pair&quot;&gt;setup a new keypair&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;gpg --gen-key&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I put in a passphrase that I generated from and saved to my Bitwarden vault.
The syntax of the &lt;code&gt;pass init&lt;/code&gt; command requires we pass a &quot;gpg-id_or_email&quot;.
It&#39;s not clear to me what the &lt;code&gt;gpg-id&lt;/code&gt; or &lt;code&gt;email&lt;/code&gt; fields really mean.
Are they referring to the email I used to create the key?
Does each GPG key have an id assigned to it?
The language in the documentation here could be more helpful.&lt;/p&gt;
&lt;p&gt;The approach I took is&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;gpg --list-secret-keys --keyid-format=long&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which results in something like:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[keyboxd]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;---------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sec   ed25519/XXXXXXXXXXXXXXXX 2026-09-28 [SC] [expires: 2029-09-27]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;uid                 [ultimate] Ben Potter &amp;lt;bp@benpot.com&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ssb   cv25519/ZZZZZZZZZZZZZZZZ 2026-09-28 [E] [expires: 2029-09-27]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have replace the secret key information with X, Y, and Z.
I copied the alphanumeric id that corresponds to the Xs and used it in the &lt;code&gt;pass init&lt;/code&gt; command.
I think we have to supply the gpg-id of the secret, so we use &lt;code&gt;--list-secret-keys&lt;/code&gt;, but I am not sure.
The alternative is to try &lt;code&gt;--list-keys&lt;/code&gt; which shows the public key id.&lt;/p&gt;
&lt;p&gt;Anyways to setup &lt;code&gt;pass&lt;/code&gt; with this GnuPG key id we do:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pass init XXXXXXXXXXXXXXXX &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://wiki.archlinux.org/title/Pass#Basic_usage&quot;&gt;https://wiki.archlinux.org/title/Pass#Basic_usage&lt;/a&gt;
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://wiki.archlinux.org/title/GnuPG&quot;&gt;https://wiki.archlinux.org/title/GnuPG&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;setup-proton-bridge&quot;&gt;Setup Proton Bridge&lt;/h3&gt;
&lt;p&gt;After &lt;code&gt;protonmail-bridge&lt;/code&gt; has been installed, I run:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;protonmail-bridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are asked for the passphrase used to setup the GnuPG key.
This is a good sign that we are on the right track.
Proton Bridge has identified that we are using &lt;code&gt;pass&lt;/code&gt; and that there is a PGP key stored on that keyring.
I am not sure how this discovery works automatically and where it might fail.
YMMV here.&lt;/p&gt;
&lt;p&gt;I signed into my Proton account and went through a few verification steps.
Once signed in, Proton Bridge provided some information about the settings I should use for my local mail client.&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Field&lt;/th&gt;&lt;th&gt;IMAP&lt;/th&gt;&lt;th&gt;SMTP&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Hostname&lt;/td&gt;&lt;td&gt;127.0.0.1&lt;/td&gt;&lt;td&gt;127.0.0.1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Port&lt;/td&gt;&lt;td&gt;1143&lt;/td&gt;&lt;td&gt;1025&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Username&lt;/td&gt;&lt;td&gt;bp@benpot.com&lt;/td&gt;&lt;td&gt;bp@benpot.com&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;em&gt;Password&lt;/em&gt;&lt;/td&gt;&lt;td&gt;XJEZ78NwZMMcNbdll2JBCA&lt;/td&gt;&lt;td&gt;XJEZ78NwZMMcNbdll2JBCA&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Security&lt;/td&gt;&lt;td&gt;STARTTLS&lt;/td&gt;&lt;td&gt;STARTTLS&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;-----&lt;/td&gt;&lt;td&gt;----&lt;/td&gt;&lt;td&gt;----&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The settings appear identical for IMAP and SMTP, except the port.
Proton Bridge serves a local IMAP and SMTP service that the local client connects to to recieve unencrypted mail that it understands.
Note that the password fields provided here &lt;em&gt;DO NOT&lt;/em&gt; correspond to the password for your Proton mail account.
They are generated locally and only apply to this connection to Proton Bridge.&lt;/p&gt;
&lt;p&gt;We can add the local password that Proton Bridge generated to our new &lt;code&gt;pass&lt;/code&gt; keyring.
Then, when we are configuring our local mail client, we don&#39;t store this password in the clear.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pass insert proton-bridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Follow the prompts to add the password to the keyring.
Test that the password was entered correctly:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pass show proton-bridge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TODO:
Note that the bridge has to be running in order to serve the local mail server and enable connection with the local mail client.
I am not certain if Proton Bridge is configured to start on boot by default.
More on this later.&lt;/p&gt;
&lt;p&gt;Now let&#39;s configure our chosen local mail client.&lt;/p&gt;
&lt;h2 id=&quot;setup-himalaya&quot;&gt;Setup Himalaya&lt;/h2&gt;
&lt;p&gt;For configuration with Proton, the automatic setup wizard does not work.
Testing it locally, I get the following error reported from the setup wizard.
We will need to configure the account manually in the &lt;code&gt;config.toml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[I] ben@thinkpad-x1 ~&amp;gt; himalaya&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Welcome to Himalaya, the CLI to manage emails.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Himalaya talks to your existing mailbox over IMAP, JMAP, Gmail, Microsoft&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Graph or a local Maildir. It needs one account to know which mailbox to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;read, and no configuration file was found at:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  /home/ben/.config/himalaya/config.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;The wizard discovers a provider&amp;#39;s settings from your email address, tests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;the connection and generates a ready-to-use account. Everything discovery&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;does not cover is written by hand, and every field is documented at:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  https://github.com/pimalaya/himalaya/blob/master/config.sample.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;At anytime, you can create a new account with the command:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  himalaya configure&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;gt; Create a configuration with a default account? Yes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;gt; Email: bp@benpot.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;✗ No configuration found&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Error: Could not automatically discover a configuration for `bp@benpot.com`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Write your account configuration by hand instead, starting from the documented sample:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  https://github.com/pimalaya/himalaya/blob/master/config.sample.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Suggestions:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; - Run with --log-level to enable more verbose logs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sample configuration file can be found &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/pimalaya/himalaya/blob/master/config.sample.toml&quot;&gt;here&lt;/a&gt;.
I copied the whole thing into my local configuration at &lt;code&gt;$XDG_CONFIG_HOME/himalaya/config.toml&lt;/code&gt;.
This is generally what I like to do as the default configuration contains lots of useful documentation.
We can just uncomment the portions that we need.&lt;/p&gt;
&lt;p&gt;This was where I ended up with the account section of the config.
I had to also comment out the placeholder IMAP/SMTP configurations that appear in the default config.
Otherwise, I was getting a &quot;duplicate&quot; error in the &lt;code&gt;imap.server&lt;/code&gt; keys and the like.
It would be nice if the global keys could be overridden when the account specific configuration is applied.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# --------------------------------------------------------------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Account config&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# --------------------------------------------------------------------------------&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# One section per account, picked with `-a/--account &amp;lt;NAME&amp;gt;` or, failing that,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# by the entry flagged `default = true`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[accounts.proton]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Use this account when `-a/--account` is not passed.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;default = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Address this account sends as, and the name it carries. They fill the `From`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# header of a composed message absent `--from`, and without them the header is&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# left out. Also spelled `from` and `from-name`, the keys himalaya-tui writes;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# both binaries read both, one file backing the two.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;email = &amp;quot;bp@benpot.com&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;display-name = &amp;quot;Ben Potter&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Signature appended after the body, and after any quoted source text, absent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# `--signature` and `--signature-file`.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#signature = &amp;quot;Best,\nAlice&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#signature-delim = &amp;quot;-- \n&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Per-account overrides for the global options above.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#downloads-dir = &amp;quot;~/downloads/example&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#table.preset = &amp;quot;││──╞═╪╡┆    ┬┴┌┐└┘&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#table.arrangement = &amp;quot;dynamic&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#envelope.list.datetime-fmt = &amp;quot;%F %R%:z&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#envelope.list.datetime-local-tz = false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Unique configuration for Proton mail.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Point to the `protonmail-bridge` server that handles interop with Proton mail.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;imap.server = &amp;quot;imap://127.0.0.1:1143&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;imap.sasl.plain.username = &amp;quot;bp@benpot.com&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;imap.sasl.plain.password.command = &amp;quot;pass show proton-bridge&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;smtp.server = &amp;quot;smtp://127.0.0.1:1025&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;smtp.sasl.plain.username = &amp;quot;bp@benpot.com&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;smtp.sasl.plain.password.command = &amp;quot;pass show proton-bridge&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also uncommented the &lt;code&gt;mailbox.alias.inbox = &quot;INBOX&quot;&lt;/code&gt; line to set the default mailbox to use if the &lt;code&gt;-m&lt;/code&gt; flag is not specified when reading mail.&lt;/p&gt;
&lt;p&gt;TODO: Add TLS certs exported from protonmail-bridge
The button to export from the GUI is not clickable.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/pimalaya/himalaya&quot;&gt;https://github.com/pimalaya/himalaya&lt;/a&gt;&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Troubleshooting DisplayLink on Linux</title>
        <published>2026-03-25T00:00:00+00:00</published>
        <updated>2026-03-25T00:00:00+00:00</updated>
        
        <author>
          <name>Ben Potter</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://benpot.com/blog/displaylink-linux/"/>
        <id>https://benpot.com/blog/displaylink-linux/</id>
        
        <content type="html" xml:base="https://benpot.com/blog/displaylink-linux/">&lt;p&gt;I got a &lt;strong&gt;Lenovo ThinkPad Hybrid USB-C with USB-A Dock&lt;/strong&gt; and plugged it into my ThinkPad X1 running Arch Linux and Sway.
Two external monitors, keyboard, mouse, and fast charging.
More importantly, the ability to (dis)connect my laptop from my desk setup with one cable.
In practice I had fans screaming, &lt;code&gt;DisplayLinkManager&lt;/code&gt; pinned at 80% CPU, and Sway running entirely on software rendering.&lt;/p&gt;
&lt;h2 id=&quot;the-initial-problem-no-display-output-at-all&quot;&gt;The Initial Problem: No Display Output At All&lt;/h2&gt;
&lt;p&gt;Out of the box, nothing worked.
The displays connected to the dock showed no signal.
This is a common first-hurdle for DisplayLink docks on Linux since the driver doesn&#39;t ship with the kernel and has to be installed manually.
In reality, this is a problem with many peripheral devices on Linux.
It&#39;s a good thing I like to get my hands dirty and play around with these things.&lt;/p&gt;
&lt;h3 id=&quot;archwiki-to-the-rescue&quot;&gt;ArchWiki to the Rescue&lt;/h3&gt;
&lt;p&gt;The ArchWiki is a great resource for addressing these problems.
They have a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://wiki.archlinux.org/title/DisplayLink#USB_3.0_DL-6xxx,_DL-5xxx,_DL-41xx,_DL-3xxx_Devices&quot;&gt;page dedicated to setting up DisplayLink drivers on Linux machines&lt;/a&gt;.
What follows is a basic summary of that page with some of my experience added in.&lt;/p&gt;
&lt;h3 id=&quot;installing-the-displaylink-driver&quot;&gt;Installing the DisplayLink Driver&lt;/h3&gt;
&lt;p&gt;The fix is to install the &lt;code&gt;displaylink&lt;/code&gt; package from the AUR, which pulls in the &lt;code&gt;evdi&lt;/code&gt; kernel module and a systemd service:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;yay&lt;/span&gt;&lt;span class=&quot;z-1&quot;&gt; -S&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; displaylink&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; systemctl enable&lt;/span&gt;&lt;span class=&quot;z-1&quot;&gt; --now&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; displaylink.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;evdi&lt;/code&gt; is a kernel module that creates virtual DRM (Direct Rendering Manager) devices.
The DisplayLink daemon (&lt;code&gt;DisplayLinkManager&lt;/code&gt;) takes those virtual devices, encodes the framebuffer data, and ships it over USB to the dock&#39;s DisplayLink chip, which then drives the physical monitors.&lt;/p&gt;
&lt;p&gt;I tried rebooting my system.
After loading the module and starting the service, Sway still wouldn&#39;t launch normally.
I am running &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https://github.com/fairyglade/ly&quot;&gt;the Ly login manager&lt;/a&gt; which detected Sway as an environment to boot into automatically.
After entering valid credentials, I was getting sent right back to the login manager.
I suspected that Sway was crashing on boot, so I opened a TTY to view the output.
it complained about unsupported GPU devices.
The fix was to start it with:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;sway&lt;/span&gt;&lt;span class=&quot;z-1&quot;&gt; --unsupported-gpu&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that flag, both external monitors came up and were fully usable.
If you want to run with this you have to update the session configuration in Ly.
I would likely opt to create a new &lt;code&gt;.desktop&lt;/code&gt; file in &lt;code&gt;/etc/ly/custom-sessions/&lt;/code&gt; and update the &lt;code&gt;Exec&lt;/code&gt; field to include the &lt;code&gt;--unsupported-gpu&lt;/code&gt; flag.&lt;/p&gt;
&lt;h2 id=&quot;the-real-problem-software-rendering-everywhere&quot;&gt;The Real Problem: Software Rendering Everywhere&lt;/h2&gt;
&lt;p&gt;The displays worked, but the system felt wrong.
The laptop fans were running hard constantly, and checking &lt;code&gt;htop&lt;/code&gt; revealed &lt;code&gt;DisplayLinkManager&lt;/code&gt; sitting at around 80% CPU usage.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;evdi&lt;/code&gt; creates virtual DRM devices, those devices have no real GPU backing them.
Sway detects this and refuses to use hardware acceleration.
The &lt;code&gt;--unsupported-gpu&lt;/code&gt; flag forces it to proceed anyway, but the fallback is &lt;strong&gt;llvmpipe&lt;/strong&gt;, which is a software renderer that runs entirely on the CPU.&lt;/p&gt;
&lt;p&gt;This means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every frame on &lt;em&gt;every&lt;/em&gt; display (including the laptop screen) is rendered in software&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DisplayLinkManager&lt;/code&gt; additionally encodes each frame and sends it over USB&lt;/li&gt;
&lt;li&gt;The CPU is doing the work of a GPU for three monitors simultaneously&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;swaymsg -t get_outputs&lt;/code&gt; confirmed the setup was working but gave away the software rendering story.
The DisplayLink outputs appeared as &lt;code&gt;DVI-I-1&lt;/code&gt; and &lt;code&gt;DVI-I-2&lt;/code&gt;, which are presumably the virtual device names created by &lt;code&gt;evdi&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-hope-maybe-the-dock-supports-native-dp-alt-mode&quot;&gt;The Hope: Maybe the Dock Supports Native DP Alt Mode?&lt;/h2&gt;
&lt;p&gt;The dock is called &quot;Hybrid&quot; for a reason.
It&#39;s designed to work with both USB-A laptops (via a dongle) and USB-C laptops.
For USB-C hosts, the spec sheet lists support for USB-C, USB4, Thunderbolt 3, and Thunderbolt 4.
This raised an obvious question: could the dock negotiate a native DisplayPort Alt Mode connection and bypass DisplayLink entirely?
If it could, the displays would be driven directly through the ThinkPad&#39;s Intel GPU over the Thunderbolt controller.
So, no &lt;code&gt;evdi&lt;/code&gt;, no software rendering, no CPU overhead.&lt;/p&gt;
&lt;p&gt;Since this didn&#39;t work on first plug-in, I was skeptical.
I am also not sure how the single USB-C cable between the laptop would work with DP Alt Mode and still support the peripherals.
My only consideration was that I had not installed the &lt;code&gt;bolt&lt;/code&gt; package available from &lt;code&gt;pacman&lt;/code&gt;.
This provides access to the &lt;code&gt;boltctl&lt;/code&gt; command which lets me enumerate the thunderbolt devices connected over USB.&lt;/p&gt;
&lt;p&gt;I don&#39;t really know how DP Alt Mode works.
The last time I tried to make it work was on an M1 Macbook running Asahi Linux.
They had not yet introduced DP Alt Mode support on the Asahi side, so that fizzled quickly.
In fact, I am not sure if DP Alt Mode has support in Asahi, even now.&lt;/p&gt;
&lt;h3 id=&quot;what-native-tb4-would-look-like&quot;&gt;What Native TB4 Would Look Like&lt;/h3&gt;
&lt;p&gt;On a proper Thunderbolt 4 dock, you&#39;d see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The dock enumerated in &lt;code&gt;boltctl list&lt;/code&gt; as a Thunderbolt device&lt;/li&gt;
&lt;li&gt;Display outputs named &lt;code&gt;DP-1&lt;/code&gt;, &lt;code&gt;DP-2&lt;/code&gt; etc. (not &lt;code&gt;DVI-I-*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;evdi&lt;/code&gt; or DisplayLink involvement at all&lt;/li&gt;
&lt;li&gt;Full hardware acceleration in Sway without &lt;code&gt;--unsupported-gpu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or at least, that&#39;s what I was hoping for.&lt;/p&gt;
&lt;h3 id=&quot;testing-it&quot;&gt;Testing It&lt;/h3&gt;
&lt;p&gt;The test was straightforward: unload &lt;code&gt;evdi&lt;/code&gt;, unplug and replug the dock, and see what happens.&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; systemctl stop displaylink.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; modprobe&lt;/span&gt;&lt;span class=&quot;z-1&quot;&gt; -r&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; evdi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-2 z-i&quot;&gt;# replug dock&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;boltctl&lt;/span&gt;&lt;span class=&quot;z-4&quot;&gt; list&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-5&quot;&gt;lsusb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The results were unambiguous.
&lt;code&gt;boltctl list&lt;/code&gt; returned &lt;strong&gt;nothing&lt;/strong&gt;.
The dock was not enumerated as a Thunderbolt device at all.
&lt;code&gt;lsusb&lt;/code&gt; showed:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Bus 002 Device 004: ID 17e9:6015 DisplayLink ThinkPad Hybrid USB-C with USB-A Dock&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The vendor ID &lt;code&gt;17e9&lt;/code&gt; is DisplayLink&#39;s USB vendor ID.
Even with the DisplayLink driver completely unloaded, the dock presented itself to the OS as a pure DisplayLink USB device.
There was also a &lt;code&gt;Lenovo Billboard Device&lt;/code&gt; visible.
From my understanding, this is a USB descriptor that the dock broadcasts when Alt Mode negotiation fails, advertising its capabilities as a fallback signal.&lt;/p&gt;
&lt;p&gt;The kernel did show a live TB4/USB4 controller in &lt;code&gt;dmesg&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;usb4_port1 (ops connector_ops [thunderbolt])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the hardware on the laptop side is capable.
The dock simply isn&#39;t using it.&lt;/p&gt;
&lt;h2 id=&quot;why-the-dock-can-t-do-native-alt-mode&quot;&gt;Why the Dock Can&#39;t Do Native Alt Mode&lt;/h2&gt;
&lt;p&gt;Reading the spec sheet more carefully may explain this.
The ThinkPad Hybrid USB-C with USB-A Dock has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2× DisplayPort outputs&lt;/li&gt;
&lt;li&gt;2× HDMI outputs&lt;/li&gt;
&lt;li&gt;A USB hub with multiple USB-A and USB-C ports&lt;/li&gt;
&lt;li&gt;Ethernet&lt;/li&gt;
&lt;li&gt;Audio jack&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From what I can gather about the behaviour of the dock.
All of those video outputs are fed by the &lt;strong&gt;DisplayLink chip inside the dock&lt;/strong&gt;.
The dock doesn&#39;t do passive Thunderbolt passthrough to its video ports.
The DisplayLink chip &lt;em&gt;is&lt;/em&gt; the video controller.
The &quot;Hybrid&quot; in the name refers to supporting both USB-A and USB-C &lt;em&gt;host connections&lt;/em&gt;, not to having two separate display subsystems.
This is fundamentally different from a Thunderbolt 4 dock, where the laptop&#39;s GPU drives the displays directly over the TB4 link and the dock is largely passive on the video path.&lt;/p&gt;
&lt;h2 id=&quot;current-state-and-what-s-next&quot;&gt;Current State and What&#39;s Next&lt;/h2&gt;
&lt;p&gt;The setup works, but at a cost.
Three displays running through DisplayLink and llvmpipe is a significant CPU overhead (enough to keep the fans audible).
I didn&#39;t notice any lag in the display output, but I only tested basic terminal actions and static webpages.
It may be the case that dynamic outputs like YouTube videos cause a more significant burden on the CPU.
The dock was expensive, so it lives to fight another day, but my search for a better solution is ongoing.&lt;/p&gt;
</content>
        
    </entry>
</feed>
