<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.freddie.rs/blog/</id>
    <title>freddie Blog</title>
    <updated>2026-07-20T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://www.freddie.rs/blog/"/>
    <subtitle>freddie Blog</subtitle>
    <icon>https://www.freddie.rs/img/mercury.png</icon>
    <entry>
        <title type="html"><![CDATA[Announcing freddie]]></title>
        <id>https://www.freddie.rs/blog/announcing-freddie/</id>
        <link href="https://www.freddie.rs/blog/announcing-freddie/"/>
        <updated>2026-07-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A newborn freddie]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" alt="A newborn freddie" src="https://www.freddie.rs/assets/images/announcing-freddie-92022f379bb1bfa2a3d11bb19dfad3ce.jpg" width="752" height="752" class="img_gjoA"></p>
<p>It is with enormous pride that we announce the arrival of freddie, a set of tools for building a bespoke control plane for your computer, weighing in at fifteen crates and growing fast.</p>
<p>He is receiving visitors, and he is ready for you to experiment with.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_iy8F" id="events-in-effects-out">Events in, effects out<a href="https://www.freddie.rs/blog/announcing-freddie/#events-in-effects-out" class="hash-link" aria-label="Direct link to Events in, effects out" title="Direct link to Events in, effects out" translate="no">​</a></h2>
<p>A freddie program responds to whatever you can observe. A keypress, an app being foregrounded, which tab you are looking at, what devices are plugged in, or something you have not thought of yet.</p>
<p>Because we're integrating events from many sources, we can easily do crazy things, such as cloning the repository you're looking at without leaving GitHub.com, or muting your microphone on Google Meet (without finding the tab!)</p>
<p>A freddie program is centered around a single, pure function: state and event in, new state and a list of effects out. And so a freddie program remains testable and easy to reason about, even as it scales and grows more complex.</p>
<h2 class="anchor anchorTargetStickyNavbar_iy8F" id="for-programmers-by-programmers">For programmers, by programmers<a href="https://www.freddie.rs/blog/announcing-freddie/#for-programmers-by-programmers" class="hash-link" aria-label="Direct link to For programmers, by programmers" title="Direct link to For programmers, by programmers" translate="no">​</a></h2>
<p>Other programs for remapping keys are configuration-driven, and that makes it difficult or impossible to handle unanticipated use cases.</p>
<p>Want to bind keys? That's fine, because these apps allow that. But, want your windows to go back where they belong the moment you connect to a monitor? You're out of luck — that's a device event, not a keybinding, and these apps don't allow you to incorporate arbitrary streams of events.</p>
<h3 class="anchor anchorTargetStickyNavbar_iy8F" id="and-thats-a-sign-of-a-deeper-problem">And that's a sign of a deeper problem.<a href="https://www.freddie.rs/blog/announcing-freddie/#and-thats-a-sign-of-a-deeper-problem" class="hash-link" aria-label="Direct link to And that's a sign of a deeper problem." title="Direct link to And that's a sign of a deeper problem." translate="no">​</a></h3>
<p>Want these keybindings to do different things in different states? Well, you'd better hope that the app exposed that aspect of the state to you. Different keybindings for different active apps? That's doable, because it's anticipated and allowlisted. But, what about custom mute/unmute keybindings for when you're in an active Google Meet call? Not possible.</p>
<p>And, in configuration-driven frameworks, you don't write functions, so your handlers don't get access to the state at all! Want one key that maximizes a window and, pressed again, puts it back exactly where it was? Then something has to remember the window's old position, in other words, it needs to be a function that is passed state.</p>
<p>Now, more folks are willing to write configuration than to write and compile a Rust program. But guess what — freddie isn't for everyone. So, if you're willing to clone a repo, make some changes and run cargo build, freddie is here to give you incredible power.</p>
<h2 class="anchor anchorTargetStickyNavbar_iy8F" id="what-a-binding-looks-like">What a binding looks like<a href="https://www.freddie.rs/blog/announcing-freddie/#what-a-binding-looks-like" class="hash-link" aria-label="Direct link to What a binding looks like" title="Direct link to What a binding looks like" translate="no">​</a></h2>
<p>A binding is a trigger and the handler it runs, written on the layer where it applies. In the nav layer, <code>c</code> foregrounds Chrome:</p>
<div class="language-rust codeBlockContainer_cCYc theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QiGi"><pre tabindex="0" class="prism-code language-rust codeBlock_jYaH thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_TDjf"><div class="token-line" style="color:#393A34"><span class="token attribute attr-name" style="color:#00a4db">#[bind(</span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attr-name" style="color:#00a4db">    Key::KeyC.down() =&gt; open_chrome,</span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attr-name" style="color:#00a4db">    Key::KeyG.down() =&gt; open_ghostty,</span><br></div><div class="token-line" style="color:#393A34"><span class="token attribute attr-name" style="color:#00a4db">)]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">pub</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">struct</span><span class="token plain"> </span><span class="token type-definition class-name">NavLayer</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>And the handler asks for exactly one effect:</p>
<div class="language-rust codeBlockContainer_cCYc theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QiGi"><pre tabindex="0" class="prism-code language-rust codeBlock_jYaH thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_TDjf"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">fn</span><span class="token plain"> </span><span class="token function-definition function" style="color:#d73a49">open_chrome</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token lifetime-annotation symbol" style="color:#36acaa">'a</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    _ev</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">&amp;</span><span class="token class-name">KeyEvent</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    _node</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token class-name">Node</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token class-name">NavLayerPath</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token lifetime-annotation symbol" style="color:#36acaa">'a</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-&gt;</span><span class="token plain"> </span><span class="token class-name">MercuryEffect</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token class-name">MercuryEffect</span><span class="token punctuation" style="color:#393A34">::</span><span class="token class-name">Foreground</span><span class="token punctuation" style="color:#393A34">(</span><span class="token class-name">App</span><span class="token punctuation" style="color:#393A34">::</span><span class="token class-name">Chrome</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>That’s the whole thing. It reads neither the event nor the state, because it doesn’t need to: it only runs when <code>c</code> went down in the nav layer, and dispatch established both of those before calling it.</p>
<p>Note that the effect isn’t a keypress. Emitting <code>cmd</code>-<code>r</code> to refresh a page is one variant of <code>MercuryEffect</code>, and foregrounding an app is another, and you write as many as you need.</p>
<p>Handlers that do need state are handed a path to the layer they were bound on. <a href="https://www.freddie.rs/" target="_blank" rel="noopener noreferrer" class="">freddie.rs</a> builds one out in full: maximizing a window, putting it back exactly where it was, and hooking up the event source that tells the model which window is focused in the first place.</p>
<h2 class="anchor anchorTargetStickyNavbar_iy8F" id="give-mercury-a-try">Give mercury a try<a href="https://www.freddie.rs/blog/announcing-freddie/#give-mercury-a-try" class="hash-link" aria-label="Direct link to Give mercury a try" title="Direct link to Give mercury a try" translate="no">​</a></h2>
<p>This repository ships one program built with freddie, called <code>mercury</code>. It is macOS-only and it requires accessibility permissions. You should not expect it to fit your use case: it is here to be read, run, studied, forked, and modified.</p>
<div class="language-bash codeBlockContainer_cCYc theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QiGi"><pre tabindex="0" class="prism-code language-bash codeBlock_jYaH thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_TDjf"><div class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">git</span><span class="token plain"> clone https://github.com/freddiehg/freddie</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">cd</span><span class="token plain"> freddie</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">cargo</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">install</span><span class="token plain"> </span><span class="token parameter variable" style="color:#36acaa">--path</span><span class="token plain"> crates/mercury</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">mercury</span><br></div></code></pre></div></div>
<p><code>mercury</code> boots into the typing layer, which passes all keystrokes through. Typing <code>jk</code> takes you to the home layer. From there, <code>n</code> takes you to nav, <code>i</code> to in-app, <code>s</code> to per-site, and <code>r</code> to resize. <code>o</code> displays an overlay containing the layer’s keymap.</p>
<p>Then open <code>crates/mercury/src/state/</code> and change something. <code>bacon restart</code> rebuilds and replaces the running daemon, so an edited binding is live without you touching a window, and <code>mercury logs</code> prints one record per dispatched event carrying the event, the effects it produced, and the state it left behind.</p>
<p>There is also <a href="https://www.youtube.com/watch?v=eM3wmsUWsbo" target="_blank" rel="noopener noreferrer" class="">a demo of freddie</a>!</p>
<p>So, are you ready, Freddie?</p>
<p><a class="" href="https://www.freddie.rs/docs/getting-started-with-mercury/">Getting Started with Mercury</a> walks through the layers, and <a class="" href="https://www.freddie.rs/docs/implementing-your-own-handler/">Implementing Your Own Handler</a> writes the first binding.</p>]]></content>
        <author>
            <name>Robert Balicki</name>
            <uri>https://github.com/rbalicki2</uri>
        </author>
        <category label="Announcements" term="Announcements"/>
    </entry>
</feed>