<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/stylesheet.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lele&apos;s log, DEE Not-Enterprise, 2026</title><description>A place for my thoughts, my trials with lots of errors, and everything in between</description><link>https://neverready.app/</link><language>en-us</language><item><title>TIL that pathnames in git configs can be optional</title><link>https://neverready.app/blog/2026/02-git-blame-ignore/</link><guid isPermaLink="true">https://neverready.app/blog/2026/02-git-blame-ignore/</guid><description>Git 2.52 includes a feature that makes working with ignore-revs-files mroe comfortable. Here is a brief explanation.</description><pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;git-blame shows what revision and author last modified each line of a file.&lt;/p&gt;
&lt;p&gt;There are cases where you may want to ignore certain revisions, e.g. when a revision only contains changes that resulted from a code formatter changing the indentation of the entire codebase.&lt;/p&gt;
&lt;p&gt;To ignore such commits, you can use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; blame&lt;/span&gt;&lt;span&gt; --ignore-rev&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;re&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, you can ignore multiple revisions from a file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; blame&lt;/span&gt;&lt;span&gt; --ignore-revs-file&lt;/span&gt;&lt;span&gt; &amp;lt;&lt;/span&gt;&lt;span&gt;fil&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By convention, the ignore-revs-file is called &lt;code&gt;.git-blame-ignore-revs&lt;/code&gt; and placed at the repository root. Git forges like &lt;a href=&quot;https://docs.github.com/en/repositories/working-with-files/using-files/viewing-and-understanding-files#ignore-commits-in-the-blame-view&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://docs.gitlab.com/user/project/repository/files/git_blame/#ignore-specific-revisions&quot;&gt;GitLab&lt;/a&gt; support this file directly in their blame views.&lt;/p&gt;
&lt;p&gt;If you want to default to &lt;code&gt;.git-blame-ignore-revs&lt;/code&gt; for the ignore-revs-file locally, you can set the config option &lt;code&gt;blame.ignorerevsfile&lt;/code&gt;... and figure out that it&apos;s a bad idea. git-blame fails if the ignore-revs-file doesn&apos;t exist - and in most repositories it doesn&apos;t exist. So don&apos;t set &lt;code&gt;blame.ignorerevsfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;... or so I thought until I read a &lt;a href=&quot;https://news.ycombinator.com/item?id=47111218#47114288&quot;&gt;comment by ilostmymangoman on hacker news&lt;/a&gt;. As of git 2.52 (Nov 2025), you can mark config file paths as optional using the &lt;code&gt;:(optional)&lt;/code&gt; prefix.&lt;/p&gt;
&lt;p&gt;That means you can safely configure an ignore-revs-file in your &lt;code&gt;~/.gitconfig&lt;/code&gt; without breaking git-blame if the file doesn&apos;t exist:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;blame&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ignoreRevsFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; :(optional).git-blame-ignore-revs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>TIL</category></item><item><title>First Impressions of Rust</title><link>https://neverready.app/blog/2026/01-first-impressions-of-rust/</link><guid isPermaLink="true">https://neverready.app/blog/2026/01-first-impressions-of-rust/</guid><description>I didn&apos;t come to Rust for speed or memory safety. As a web developer, I came out of curiosity and stayed for the language</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Rust has been the &quot;most admired&quot; language in StackOverflow&apos;s surveys for 10 years straight&lt;sup&gt;&lt;a href=&quot;#user-content-fn-rust-popularity&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. This sounds impressive. But the surveys don&apos;t tell you &lt;em&gt;why&lt;/em&gt;. You can find plenty of opinions online, but as the curious mind I wish I were, I wanted to see for myself what makes people love Rust so much. So I started hacking on a little side project: &lt;a href=&quot;https://github.com/Leleat/git-forge&quot;&gt;git-forge&lt;/a&gt;, a CLI tool to interact with git forges using a unified interface. I wrote about it in my &lt;a href=&quot;https://leleat.github.io/blog/2025/03-git-forge/&quot;&gt;previous blog post&lt;/a&gt;. It was a nice way to dip my toes into Rust without getting overwhelmed by a giant existing codebase - especially since Rust is my first &quot;systems programming language&quot;. In my day job, I&apos;m a web developer with JavaScript, TypeScript, and a bit of Java as my main languages.&lt;/p&gt;
&lt;p&gt;After writing Rust for a bit, I have to say that I like it - but not for the reasons people usually go to Rust for.&lt;/p&gt;
&lt;p&gt;Rust&apos;s claim to fame is performance, efficiency, and memory safety without garbage collection. Frankly, I don&apos;t care for any of that. Not everyone operates at a scale where you need to squeeze out every last drop of performance, and I don&apos;t hit any limits with my current tech stack.&lt;/p&gt;
&lt;p&gt;I like Rust because it&apos;s simply a pleasant language to use. Maybe I&apos;ll dislike it when I need to write my own macros, when I fight the borrow checker and worry about lifetimes more often, or when I deep-dive into concurrent and parallel programming... but it is not this day! This day we fight I like Rust. There are plenty of reasons for this. But let&apos;s go with the classic approach, and start with the bad and end with the good.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-bad&quot;&gt;The Bad&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The initial experience with Rust is nice. There are plenty of resources for beginners like the &lt;a href=&quot;https://doc.rust-lang.org/stable/book/&quot;&gt;Rust Book&lt;/a&gt; or the &lt;a href=&quot;https://github.com/rust-lang/rustlings/&quot;&gt;Rustlings exercises&lt;/a&gt;. There is plenty of handholding and documentation. But eventually there&apos;s none of that. Eventually, you fight with the ownership system and the borrow checker... even though at first they sounded easy conceptually and looked manageable thanks to lifetime elision. Eventually, you look at other people&apos;s code and feel like you&apos;re studying an arcane magic book... even though you understand the syntax (in theory). Error messages are supposedly helpful... but only if someone (e.g. the Rust Book) points you to the exact sentence that matters. When you&apos;re on your own, you can feel lost in the noise.&lt;/p&gt;
&lt;p&gt;I expect these struggles to ease with experience. What I&apos;m less sure will improve is Rust&apos;s standard library. When I say &quot;standard library&quot;, I don&apos;t mean &lt;code&gt;std&lt;/code&gt; strictly; I mean what&apos;s officially supported by Rust. It feels tiny. I&apos;d be completely fine with a small &lt;code&gt;std&lt;/code&gt; if there were first-party opt-in crates maintained by the Rust team. Even for &lt;code&gt;git-forge&lt;/code&gt;, which is relatively trivial, I needed to reach for a few libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;anyhow&lt;/code&gt; for convenient error handling&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clap&lt;/code&gt; for argument parsing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reqwest&lt;/code&gt; for making HTTP requests&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serde&lt;/code&gt; for (JSON) de/serialization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While I use other dependencies like &lt;code&gt;ratatui&lt;/code&gt;, those four are the ones that hurt because they feel basic to me. My initial TypeScript implementation of &lt;code&gt;git-forge&lt;/code&gt; had zero dependencies except for &lt;code&gt;Node&lt;/code&gt; itself. It&apos;s obvious why Node includes tools for making HTTP requests and JSON de/serialization, but &lt;a href=&quot;https://nodejs.org/api/util.html#utilparseargsconfig&quot;&gt;Node even includes simple argument parsing&lt;/a&gt;, which is great for things beyond basic shell scripts but short of full-blown &quot;apps&quot;.&lt;/p&gt;
&lt;p&gt;Reaching for dependencies always makes me a little uneasy. While I try to do &lt;em&gt;some&lt;/em&gt; due diligence - checking who maintains a project, how many maintainers and contributors it has, how actively it&apos;s maintained - there&apos;s always a chance for something to go wrong. Just look at npm, which has seen multiple notable supply chain attacks recently&lt;sup&gt;&lt;a href=&quot;#user-content-fn-npm-attack&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. It&apos;s hard not to worry about the Jenga tower of dependencies behind many projects.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;xkcd: dependency&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;It may be only a matter of time until we read about attacks on crates.io. Time will tell...&lt;/p&gt;
&lt;p&gt;Those are my only real gripes with Rust at the moment. The list of things I enjoy is much longer.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-good&quot;&gt;The Good&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I appreciate Rust&apos;s defaults, e.g. privacy and immutability. I like &lt;code&gt;notUsingCamelCaseForReadabilityButInstead&lt;/code&gt; &lt;code&gt;using_snake_case_which_is_more_readable_even_when_using_absurdly_long_names&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I like that Rust is expression-based. You don&apos;t need the ternary operator, just use if expressions (or other control flow). You don&apos;t need an IIFE (Immediately Invoked Function Expression) to create a new scope; just create a block that automatically evaluates to its last expression.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* TypeScript */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ternary operator&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; isActive &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &quot;active&quot;&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; &quot;inactive&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// IIFE for complex initialization&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (()&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; getBaseConfig&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ... more setup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        /* ... */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* Rust */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ternary -&amp;gt; normal if-else expression&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; is_active &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; &quot;active&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; &quot;inactive&quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// IIFE -&amp;gt; just a block&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; base &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; get_base_config&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ... more setup&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Config&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; /* ... */&lt;/span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt; // block evaluates to this&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then there&apos;s pattern matching and exhaustive checks. They felt a bit foreign at first. They don&apos;t come as naturally in the languages I&apos;m used to. But now I wouldn&apos;t want to go without them&lt;sup&gt;&lt;a href=&quot;#user-content-fn-pattern-exhaustive-java-ts&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* Rust */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;enum&lt;/span&gt;&lt;span&gt; Status&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; Active&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; Inactive&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; Pending&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt; describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Status&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    match&lt;/span&gt;&lt;span&gt; status &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        Status&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Active&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;active&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        Status&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;Inactive&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;inactive&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // Compiler error: non-exhaustive pattern, `Pending` not covered.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust&apos;s module system was another pleasant surprise. I always found it awkward in JavaScript/TypeScript that &lt;code&gt;export&lt;/code&gt; means &quot;public to the entire codebase&quot;. You need to rely on conventions (e.g. create an &lt;code&gt;index.js&lt;/code&gt; file to declare a &quot;soft&quot; public API), keep all module code in a giant file, or break the code out into a separate library.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* TypeScript */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/utils_module/internal_to_utils_module.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; internal_util_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// This is allowed&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/utils_module/another_util.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; internal_util_function&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;./internal_to_utils_module.ts&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// But this is also allowed and can&apos;t be prevented :(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// src/some_module/index.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; internal_util_function&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;../utils_module/internal_to_utils_module.ts&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust gives you more fine-grained control. It lets you cleanly separate modules within a project. Child modules automatically hide their implementation details from parent modules, while still being able to access parent code. So you can selectively (re)export things to create a proper public API. Here&apos;s the same example in Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/* Rust */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// For simplicity, all modules are put into a single file, but you&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// could split them out into separate files.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mod&lt;/span&gt;&lt;span&gt; utils_module&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mod&lt;/span&gt;&lt;span&gt; internal_to_utils_module&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        pub&lt;/span&gt;&lt;span&gt; fn&lt;/span&gt;&lt;span&gt; internal_util_function&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    mod&lt;/span&gt;&lt;span&gt; another_util_module&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        fn&lt;/span&gt;&lt;span&gt; some_func&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // internal_to_utils_module made internal_util_function pub&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // for the utils_module. So another_util_module can access it&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            // as well since it can access everything in utils_module.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            super&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;internal_to_utils_module&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;internal_util_function&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;mod&lt;/span&gt;&lt;span&gt; some_module&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // utils_module neither re-exported internal_util_function nor made&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // internal_to_utils_module public. That means, some_module can&apos;t&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // access internal_util_function :)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust&apos;s type system is another highlight. You can encode a lot of useful information to &quot;make illegal states unrepresentable&quot;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Result&lt;/code&gt; type forces you to acknowledge possible failures instead of being surprised at runtime. I enjoy the idea so much that I tried to implement it in TypeScript (as have others). But it isn&apos;t as ergonomic as it is in Rust without pattern matching and the &lt;code&gt;?&lt;/code&gt; operator to propagate errors concisely.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Option&lt;/code&gt; type explicitly represents the absence of a value. You can rely on a &lt;code&gt;String&lt;/code&gt; being a &lt;code&gt;String&lt;/code&gt;. Compare that to Java, where &lt;code&gt;Optionals&lt;/code&gt; themselves can be &lt;code&gt;null&lt;/code&gt;, and where you need to rely on third-party tools like &lt;a href=&quot;https://github.com/uber/NullAway&quot;&gt;NullAway&lt;/a&gt; to improve null safety.&lt;/p&gt;
&lt;p&gt;The trait system initially looked like interfaces to me because they share the same goal: sharing behavior. But traits &lt;a href=&quot;https://stackoverflow.com/questions/69477460/is-rust-trait-the-same-as-java-interface&quot;&gt;offer features that interfaces don&apos;t&lt;/a&gt;. My favorite is that you can implement your own traits for foreign types extending their implementation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Define your own trait&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;trait&lt;/span&gt;&lt;span&gt; Slugify&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fn&lt;/span&gt;&lt;span&gt; slugify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Implement it for String (from std)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;impl&lt;/span&gt;&lt;span&gt; Slugify&lt;/span&gt;&lt;span&gt; for&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fn&lt;/span&gt;&lt;span&gt; slugify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; -&amp;gt;&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_lowercase&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &quot;-&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Now you can call it like a method&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; title &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My Blog Post&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; slug &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; title&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slugify&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;span&gt; // &quot;my-blog-post&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// In other languages, you&apos;d write a standalone function like&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// slugify(title) or modify String.prototype (in JS/TS), which pollutes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// the prototype&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rust&apos;s tooling is another plus. You get &lt;strong&gt;built-in&lt;/strong&gt; linting, code formatting, documentation, package management, and testing. Compared to JS/TS/Java, where you need third-party tools. Rust also has features like &lt;a href=&quot;https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html&quot;&gt;documentation tests&lt;/a&gt; or inline &lt;a href=&quot;https://doc.rust-lang.org/book/ch11-01-writing-tests.html&quot;&gt;(in-file) tests&lt;/a&gt; that I haven&apos;t commonly seen elsewhere. I especially like the inline tests. I can implement a function and verify it works without setting up a new test file or making the function public. Whether to keep those tests in the end is a different matter, but being able to test an implementation quickly and easily is great.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-end&quot;&gt;The End&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Of course, none of these features are unique to Rust. Functional languages, strongly (and statically) typed languages, or &lt;em&gt;insert-language-x&lt;/em&gt; offer many of them. I just haven&apos;t used any of those languages before; they always felt too different, too niche, or too &lt;em&gt;something-else&lt;/em&gt;. Rust as a whole feels approachable and familiar enough. It strikes a nice balance between what it offers and its tradeoffs. If my future experience with Rust is as enjoyable as it has been so far, it might become my go-to language for new projects. I would recommend Rust - not because of its performance or safety (after all, I am unfit to speak about that) - but because &lt;strong&gt;Rust is simply a pleasant language to use&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;section&gt;&lt;h2&gt;&lt;a href=&quot;#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.blog/developer-skills/programming-languages-and-frameworks/why-rust-is-the-most-admired-language-among-developers/&quot;&gt;GitHub&apos;s Blog Post&lt;/a&gt;, which reviews the 2023 survey, &lt;a href=&quot;https://survey.stackoverflow.co/2024/technology#admired-and-desired&quot;&gt;SO survey 2024&lt;/a&gt;, and &lt;a href=&quot;https://survey.stackoverflow.co/2025/technology#admired-and-desired&quot;&gt;SO survey 2025&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-rust-popularity&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.stepsecurity.io/blog/supply-chain-security-alert-eslint-config-prettier-package-shows-signs-of-compromise&quot;&gt;July 18, 2025&lt;/a&gt;, &lt;a href=&quot;https://www.wiz.io/blog/s1ngularity-supply-chain-attack&quot;&gt;Nx in August 2025&lt;/a&gt;, &lt;a href=&quot;https://socket.dev/blog/tinycolor-supply-chain-attack-affects-40-packages&quot;&gt;Shai-Hulud in September 2025&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com/en-us/security/blog/2025/12/09/shai-hulud-2-0-guidance-for-detecting-investigating-and-defending-against-the-supply-chain-attack/&quot;&gt;Shai-Hulud 2.0 in November 2025&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-npm-attack&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Well, technically, Java has some pattern matching and exhaustive switch expressions. TypeScript can use a &lt;code&gt;never&lt;/code&gt; function to create an exhaustive switch or use lint rules. But these features aren&apos;t as powerful or ubiquitous as they are in Rust. &lt;a href=&quot;#user-content-fnref-pattern-exhaustive-java-ts&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>diary</category></item><item><title>git-forge: Faster Issues and PRs From Your Terminal</title><link>https://neverready.app/blog/2025/03-git-forge/</link><guid isPermaLink="true">https://neverready.app/blog/2025/03-git-forge/</guid><description>git-forge is a minimal, forge-agnostic CLI that brings issue and PR utilities directly into git</description><pubDate>Tue, 09 Dec 2025 18:45:00 GMT</pubDate><content:encoded>&lt;p&gt;I often want to drop issue links into commit messages. Doing that normally means leaving your editor, opening a browser, navigating to the repo, finding the issue, and then copying the URL... a minor inconvenience, but one that happens often enough to be mildly annoying. I&apos;m not the only one to feel this way. There are tools to address it. But they don&apos;t quite fit my needs: they either only work with one forge or don&apos;t offer the features I want. So I built git-forge.&lt;/p&gt;
&lt;p&gt;git-forge is a small CLI that plugs into git via the git-&amp;lt;subcommand&amp;gt; pattern&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Drop it somewhere in your &lt;code&gt;$PATH&lt;/code&gt; and you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; forge&lt;/span&gt;&lt;span&gt; [&amp;lt;subcommand&amp;gt;] &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works with GitHub, GitLab, Gitea, and Forgejo. If you only use GitHub, &lt;a href=&quot;https://cli.github.com/&quot;&gt;gh&lt;/a&gt; will give you signifcantly more features; but if you hop between forges, git-forge aims to be &lt;em&gt;good enough&lt;/em&gt;. Here&apos;s a quick overview of what it can do.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#features&quot;&gt;Features&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git forge issue&lt;/code&gt; fetches issues as TSV (Tab-separated values) so you can pipe them into whatever tools you already use. This makes it easy to search, filter, or copy links.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# In a repo for https://gitlab.gnome.org/GNOME/gnome-shell&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt; forge&lt;/span&gt;&lt;span&gt; issue&lt;/span&gt;&lt;span&gt; --per-page&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;8883&lt;/span&gt;&lt;span&gt; Brightness&lt;/span&gt;&lt;span&gt; management&lt;/span&gt;&lt;span&gt; is&lt;/span&gt;&lt;span&gt; broken&lt;/span&gt;&lt;span&gt; when&lt;/span&gt;&lt;span&gt; there&lt;/span&gt;&lt;span&gt; is&lt;/span&gt;&lt;span&gt; more&lt;/span&gt;&lt;span&gt; than&lt;/span&gt;&lt;span&gt; one&lt;/span&gt;&lt;span&gt; options&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; /sys/class/backlight&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8883&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;8881&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt; sometimes&lt;/span&gt;&lt;span&gt; when&lt;/span&gt;&lt;span&gt; switching&lt;/span&gt;&lt;span&gt; desktops&lt;/span&gt;&lt;span&gt; using&lt;/span&gt;&lt;span&gt; keyboard&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8881&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;8877&lt;/span&gt;&lt;span&gt; Occasional&lt;/span&gt;&lt;span&gt; segfault&lt;/span&gt;&lt;span&gt; at&lt;/span&gt;&lt;span&gt; login,&lt;/span&gt;&lt;span&gt; surrounded&lt;/span&gt;&lt;span&gt; by&lt;/span&gt;&lt;span&gt; object&lt;/span&gt;&lt;span&gt; errors&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;&quot;An object is already exported&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;assertion &apos;G_IS_OBJECT (object)&apos; failed&quot;&lt;/span&gt;&lt;span&gt;)	https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8877&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;8873&lt;/span&gt;&lt;span&gt; Passwords&lt;/span&gt;&lt;span&gt; and&lt;/span&gt;&lt;span&gt; keys&lt;/span&gt;&lt;span&gt; desktop&lt;/span&gt;&lt;span&gt; file&lt;/span&gt;&lt;span&gt; not&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; Utilities&lt;/span&gt;&lt;span&gt; folder&lt;/span&gt;&lt;span&gt; because&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; wrong&lt;/span&gt;&lt;span&gt; desktop&lt;/span&gt;&lt;span&gt; file&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8873&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;8870&lt;/span&gt;&lt;span&gt; Follow&lt;/span&gt;&lt;span&gt; gtk-interface-reduced-motion&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/8870&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git forge pr&lt;/code&gt; has three actions.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git forge pr checkout &amp;lt;pr-number&amp;gt;&lt;/code&gt; checks out a pull request locally. I prefer reviewing code in my own dev environment, but I always forget the exact ref syntax for PRs.Is it &lt;code&gt;pulls/&amp;lt;id&amp;gt;/head&lt;/code&gt;, &lt;code&gt;pull/&amp;lt;id&amp;gt;/head&lt;/code&gt;, &lt;code&gt;pull-requests/&amp;lt;id&amp;gt;/head&lt;/code&gt;? And what about GitLab, Forgejo, or Gitea? Now, I don&apos;t need to look it up anymore. git-forge streamlines this.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git forge pr list&lt;/code&gt; prints a list of pull requests as TSV, just like the issue command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# In a repo of https://gitlab.gnome.org/GNOME/gnome-shell&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt; forge&lt;/span&gt;&lt;span&gt; pr&lt;/span&gt;&lt;span&gt; --per-page&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3993&lt;/span&gt;&lt;span&gt; screenShield:&lt;/span&gt;&lt;span&gt; Fix&lt;/span&gt;&lt;span&gt; user&lt;/span&gt;&lt;span&gt; deselection&lt;/span&gt;&lt;span&gt; after&lt;/span&gt;&lt;span&gt; idle&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; greeter&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3993&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3990&lt;/span&gt;&lt;span&gt; messageList:&lt;/span&gt;&lt;span&gt; Preserve&lt;/span&gt;&lt;span&gt; MPRIS&lt;/span&gt;&lt;span&gt; art&lt;/span&gt;&lt;span&gt; aspect&lt;/span&gt;&lt;span&gt; ratio&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3990&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3984&lt;/span&gt;&lt;span&gt; layout:&lt;/span&gt;&lt;span&gt; Simplify&lt;/span&gt;&lt;span&gt; startup&lt;/span&gt;&lt;span&gt; path&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; animations&lt;/span&gt;&lt;span&gt; are&lt;/span&gt;&lt;span&gt; disabled&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3984&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3980&lt;/span&gt;&lt;span&gt; unlockDialog:&lt;/span&gt;&lt;span&gt; Tweak&lt;/span&gt;&lt;span&gt; lock&lt;/span&gt;&lt;span&gt; screen&lt;/span&gt;&lt;span&gt; when&lt;/span&gt;&lt;span&gt; limit&lt;/span&gt;&lt;span&gt; reached&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3980&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3979&lt;/span&gt;&lt;span&gt; shellDBus:&lt;/span&gt;&lt;span&gt; Add&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; BrightnessChanged&lt;/span&gt;&lt;span&gt; signal&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; brightness&lt;/span&gt;&lt;span&gt; interface&lt;/span&gt;&lt;span&gt;	https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3979&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git forge pr open&lt;/code&gt; opens a PR for the current branch. It&apos;s still somewhat experimental; I need to test it more (manually and automatically), especially on non-GitHub forges.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git forge web&lt;/code&gt; gives you quick access to URLs for the repo, issues page, or PR page.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# In a repo of https://gitlab.gnome.org/GNOME/gnome-shell&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; git&lt;/span&gt;&lt;span&gt; forge&lt;/span&gt;&lt;span&gt; web&lt;/span&gt;&lt;span&gt; --type=repository&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://gitlab.gnome.org/GNOME/gnome-shell&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because git-forge is just a CLI, you can combine it with other commands. Some git aliases for inspiration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# git aliases in .gitconfig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;alias&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fcpissue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;!git forge issue | fzf | cut -f 2 | copy&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fopenissue&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;!git forge issue | fzf | cut -f 2 | xargs xdg-open &amp;amp;&amp;gt; /dev/null&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    freviewpr&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;!git forge pr | fzf | cut -d&apos; &apos; -f 1 | xargs git forge pr checkout&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, &lt;code&gt;git fcpissue&lt;/code&gt; lets you fuzzy-search issues and then copies the URL to your clipboard so that you can add it to a commit message. &lt;code&gt;git freviewpr&lt;/code&gt; lets you fuzzy-search PRs and checks the selected one out locally.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#quirks-and-notes&quot;&gt;Quirks and Notes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;git-forge tries to provide a common feature set across forges. But the forge APIs differ. For instance, GitHub&apos;s &quot;issues&quot; API returns both issues and PRs, so git-forge has to filter them client-side. That means requesting 100 items might give you fewer actual issues compared to GitLab, which separates them server-side. In practice, I think these quirks are minor.&lt;/p&gt;
&lt;p&gt;git-forge is currently written in TypeScript. It doesn&apos;t rely on external dependencies (other than dev dependencies), but it does require Node to run. Node may be a turn-off for some people, but it&apos;s what I&apos;m most confident in and it let me focus on the functionality first. I plan to rewrite git-forge in Rust. It&apos;s partly to learn and practice Rust, partly to drop the node runtime dependency. The rewrite can focus entirely on the technical implementation without changing the feature set.&lt;/p&gt;
&lt;p&gt;[Edit]
The &lt;a href=&quot;https://github.com/Leleat/git-forge/pull/12&quot;&gt;Rust rewrite&lt;/a&gt; is complete.
[/Edit]&lt;/p&gt;
&lt;p&gt;If git-forge sounds interesting to you, you can find the project on &lt;a href=&quot;https://github.com/Leleat/git-forge&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;&lt;a href=&quot;#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://git.github.io/htmldocs/howto/new-command.html&quot;&gt;https://git.github.io/htmldocs/howto/new-command.html&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>projects</category></item><item><title>Redesigning my Little Corner of the Web</title><link>https://neverready.app/blog/2025/02-redesign-of-website/</link><guid isPermaLink="true">https://neverready.app/blog/2025/02-redesign-of-website/</guid><description>I gave my blog a small overhaul, going from a chronological list to a category-based layout. Here&apos;s what changed and why.</description><pubDate>Mon, 08 Dec 2025 18:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve recently redesigned my website. I went for a more terminal-y look. Here are some screenshots showing the old and new look.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;Old Homepage&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;New Homepage&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Layout-wise, my blog used to be very simple: just a list of all my posts in reverse chronological order. Yesterday, I read a &lt;a href=&quot;https://lalitm.com/post/homepage-redesign-light-mode-and-more/&quot;&gt;blog post by Lalit Maganti&lt;/a&gt; about his own redesign, where he links to a &lt;a href=&quot;https://neugierig.org/software/blog/2011/09/blog-format.html&quot;&gt;post by Evan Martin&lt;/a&gt;. In that post, Martin argues that having a reverse-chronological homepage doesn&apos;t really make sense when your writing covers unrelated topics. And I do see his point, which is why I decided to rethink the layout of my blog as well.&lt;/p&gt;
&lt;p&gt;Earlier this week, I had already added categories after reading Simon Willison&apos;s &lt;a href=&quot;https://simonwillison.net/2022/Nov/6/what-to-blog-about/&quot;&gt;What to blog about&lt;/a&gt;. My categories are few and fixed in number. So they are a natural way to structure the landing page.&lt;/p&gt;
&lt;p&gt;So now, the page shows the three most recent posts in each category.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;Old Blog&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;New Blog&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I tried a multi-column layout, but because the category descriptions and post cards vary in length, posts and text were misaligned. I didn&apos;t like that, so I went with a single-column design instead. Since the landing page no longer lists all posts, I also added an archive, which is basically the old view: a full reverse-chronological list.&lt;/p&gt;
&lt;p&gt;And lastly, loosly inspired by Simon Willison&apos;s &lt;a href=&quot;https://simonwillison.net/2024/Dec/22/link-blog/&quot;&gt;Link Blog&lt;/a&gt; and Blogrolls, I added a section in the sidebar with links to posts I&apos;ve enjoyed reading. I&apos;m calling it &quot;Across the Blog-verse&quot; (a reference to Across the Spider-Verse). It&apos;s a manually curated list, and I&apos;ll update it whenever I come across something great. Check it out &lt;a href=&quot;/blog/blogroll/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>diary</category></item><item><title>Reaching for the Stars with Astro</title><link>https://neverready.app/blog/2025/01-reaching-for-the-stars-with-astro/</link><guid isPermaLink="true">https://neverready.app/blog/2025/01-reaching-for-the-stars-with-astro/</guid><description>I rebuilt my Jekyll blog with Astro after comparing it against Eleventy... and immediately shipped a bug.</description><pubDate>Sat, 01 Mar 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Flashy ⭐⭐⭐ nonsense title aside, I&apos;ve switched from Jekyll to Astro as my static site generator and wanted to share my reasons behind the switch and my experience with it.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#background&quot;&gt;Background&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Previously, my website consisted of only a blog built with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; using the &lt;a href=&quot;https://github.com/cotes2020/jekyll-theme-chirpy&quot;&gt;chirpy theme&lt;/a&gt; without any customization from my end. The theme looks great but now I wanted to add a bit of a personal touch, create a homepage, and customize the styling.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;Demo of chirpy theme&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Jekyll is written in Ruby. I don&apos;t know Ruby or its ecosystem. While everything I wanted to do right now would be feasible in Jekyll, I wanted to switch to a tool that &lt;em&gt;I&lt;/em&gt; can more easily work with, i.e., one that is JavaScript/TypeScript-based. Plus, having a fun small side project certainly was another motivating factor 🙃. So, I went and looked around to see what static site generators (SSG) exist. My requirements were that it is simple and lightweight (no JS by default), not be tied to a specific frontend framework, and little to zero-config. After a bit of research, I narrowed down my choice to &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; and &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;. While I went with Astro, Eleventy definitely has some benefits over it.&lt;/p&gt;
&lt;p&gt;Eleventy has noticeably faster build times (according to Eleventy &lt;a href=&quot;https://www.11ty.dev/docs/performance/#build-performance&quot;&gt;themselves&lt;/a&gt;). They take 1.93 seconds to build 4.000 markdown files, while Astro needs 22.9 seconds. That&apos;s definitely impressive, but not something that matters for my personal website. I doubt I will ever come close to a number of files that would increase the build time to more than a few seconds.&lt;/p&gt;
&lt;p&gt;Additionally, &lt;a href=&quot;https://www.11ty.dev/#why-should-you-use-eleventy&quot;&gt;Eleventy&apos;s philosophy&lt;/a&gt; appears more personal and friendly, while Astro feels more professional and corporate. For instance, Eleventy doesn&apos;t collect telemetry, while Astro does... but they do ask if you want to opt-out.&lt;/p&gt;
&lt;p&gt;But the deciding factor in favor of Astro was the documentation. Astro has a simple hands-on &lt;a href=&quot;https://docs.astro.build/en/tutorial/0-introduction/&quot;&gt;tutorial to build a blog&lt;/a&gt; on their website. It contains everything you need from A-Z. It&apos;s a killer feature. Their sidebar is also structured very well. Eleventy, on the other hand, has a huge sidebar. It contains like 3 times the items of Astro&apos;s sidebar. It&apos;s overwhelming. Also, their tutorials link to YouTube videos (partially by third parties and partially quite old) or GitHub repos. Both don&apos;t feel as well integrated or beginner-friendly as Astro&apos;s official on-site, step-by-step tutorial.&lt;/p&gt;
&lt;p&gt;That said, one thing I do want to highlight about Eleventy&apos;s documentation is that they feature it very prominently on their homepage. It is &lt;em&gt;hard&lt;/em&gt; to miss... and it even sticks to the top of the screen when you scroll 😂&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;figcaption&gt;Elventy&apos;s documentation button&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#the-experience-of-using-astro-for-the-first-time&quot;&gt;The Experience of Using Astro for the First Time&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The developer experience of Astro is amazing. They have a great &lt;a href=&quot;https://docs.astro.build/en/tutorial/0-introduction/&quot;&gt;tutorial&lt;/a&gt; which only takes about 30 minutes or so and teaches you everything you need to build static sites. If you know HTML, CSS, and JS, you can jump straight into it and you will breeze right through because Astro is very straightforward to use. Their component/templating language is &lt;a href=&quot;https://docs.astro.build/en/reference/astro-syntax/&quot;&gt;JSX-like&lt;/a&gt;, which is nice. After all, JSX is basically just HTML + JS. The only difference I&apos;ve noticed is that you don&apos;t use camelCase but kebab-case, making this even more similar to HTML.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/styling/&quot;&gt;Styling&lt;/a&gt; is just CSS, which is scoped by default (nice!). Options for libraries like Tailwind and preprocessors are available when needed.&lt;/p&gt;
&lt;p&gt;I didn&apos;t need to edit my markdown files to accommodate Astro. In fact, I could even remove &lt;code&gt;{% raw %}&lt;/code&gt; and &lt;code&gt;{% endraw %}&lt;/code&gt; which came from Jekyll/Liquid. Customizing the markdown-to-HTML build process is possible with &lt;a href=&quot;https://docs.astro.build/en/guides/markdown-content/#markdown-plugins&quot;&gt;plugins&lt;/a&gt;, which you can write yourself. I&apos;ve used plugins to add a copy button to code sections and to auto-link headings.&lt;/p&gt;
&lt;p&gt;Providing an RSS feed for my blog was easy as there is an official package and &lt;a href=&quot;https://docs.astro.build/en/recipes/rss/&quot;&gt;guide&lt;/a&gt;. Deploying to GitHub Pages is just as simple since Astro provides a &lt;a href=&quot;https://docs.astro.build/en/guides/deploy/github/&quot;&gt;workflow template and maintains an official GitHub Action&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now let&apos;s talk about issues I ran into when using Astro... there were none, which, I guess, isn&apos;t surprising. After all, I only used Astro to generate a homepage and a blog, which are the most basic things you can do with a SSG. So I wouldn&apos;t expect anyone to run into any issues when using any SSG for that use case. The only challenge was, that I needed to style everything myself, which is the price you pay when you don&apos;t use a theme and want to have full control.&lt;/p&gt;
&lt;p&gt;[Edit]
Well, just after publishing this post. I&apos;ve noticed an issue. There is a difference between the dev environment and the production code that runs. The images in the production were broken because I&apos;ve used absolute paths when referencing them. In the dev environment that worked... 😅
[/Edit]&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#future-plans&quot;&gt;Future Plans&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My site itself is still a work-in-progress, e.g., I am missing a light mode; there are possible accessibility concerns since I used some &lt;code&gt;input&lt;/code&gt; element hacks to stick to HTML-and-CSS-only as much as possible; and my project showcase isn&apos;t done yet.&lt;/p&gt;
&lt;p&gt;As for my usage of Astro, I am pretty happy with that except that I still need to handle videos and images better. Another thing on my TODO list is to explore the &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;content collections API&lt;/a&gt; because that seems to be the perfect fit for a blog. Let me quote Astro for the benefits of using content collections here&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Collections help to organize and query your documents, enable Intellisense and type checking in your editor, and provide automatic TypeScript type-safety for all of your content.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, all those things are for future-me. My site works well enough at the moment.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#hindsight-2020&quot;&gt;Hindsight: 20/20&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Overall, my switch to Astro went great thanks its simplicity and documentation. I would recommend Astro, it&apos;s a 20/20 for me. If you know HTML, CSS, and JS, you need to learn very little to use it. Now I have full control over the look and feel of my website - with all the pros and cons that come with said control.&lt;/p&gt;</content:encoded><category>diary</category></item><item><title>Keyboard shortcut sequences in GNOME extensions</title><link>https://neverready.app/blog/2024/02-keyboard-shortcut-sequences-in-gnome-extensions/</link><guid isPermaLink="true">https://neverready.app/blog/2024/02-keyboard-shortcut-sequences-in-gnome-extensions/</guid><description>Multi-stage keyboard shortcuts like Ctrl+Del → w exist in various apps. I prototyped this for GNOME extensions, where it&apos;s not natively supported.</description><pubDate>Sun, 25 Aug 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been prototyping what I call &lt;em&gt;keyboard shortcut sequences&lt;/em&gt; in a GNOME extension. I grew fond of them while using certain apps and wanted to document my results here.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#motivation&quot;&gt;Motivation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In modern operating systems and most apps, keyboard shortcuts only consist of a single combination of possibly multiple modifier keys and &lt;strong&gt;one&lt;/strong&gt; non-modifier key. Modifiers are keys like &lt;code&gt;Ctrl&lt;/code&gt; or &lt;code&gt;Shift&lt;/code&gt; and non-modifier keys are, well, the remaining keys. An example of this type of keyboard shortcut is &lt;code&gt;Ctrl C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Keyboard shortcuts are easier to remember if the keys can serve as a mnemonic or if there is some kind of system to them. For instance, &lt;code&gt;Ctrl C&lt;/code&gt; to &lt;code&gt;c&lt;/code&gt;opy, &lt;code&gt;Ctrl S&lt;/code&gt; to &lt;code&gt;s&lt;/code&gt;ave, &lt;code&gt;Ctrl F&lt;/code&gt; to &lt;code&gt;f&lt;/code&gt;ind, &lt;code&gt;Ctrl P&lt;/code&gt; to &lt;code&gt;p&lt;/code&gt;rint, and so on. But there are only so many permutations of different modifier keys and a single non-modifier key. Eventually, you&apos;ll end up with shortcuts that appear arbitrary and unsystematic, making them harder to remember.&lt;/p&gt;
&lt;p&gt;This is where keyboard shortcut sequences come in. Shortcuts aren&apos;t triggered by a single key combination, but by a sequence of key combinations. This way, you can create a (hierarchical) system for shortcuts. Let&apos;s take &lt;code&gt;Ctrl Del&lt;/code&gt; ➝ &lt;code&gt;w&lt;/code&gt; as an example for a shortcut sequence. This shortcut would be triggered like this&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Hold &lt;code&gt;Ctrl&lt;/code&gt; and then press &lt;code&gt;Backspace&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Release &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;Backspace&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;w&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An action for this shortcut could be to delete the next word in an text editor; &lt;code&gt;Ctrl Del&lt;/code&gt; ➝ &lt;code&gt;l&lt;/code&gt; could delete the next line; and &lt;code&gt;Ctrl Del&lt;/code&gt; ➝ &lt;code&gt;p&lt;/code&gt; could delete an entire paragraph. This looks easy to remember, right? You basically only need to remember 1 key combination (&lt;code&gt;Ctrl Backspace&lt;/code&gt;). It&apos;s certainly easier than remembering 3 random looking shortcuts.&lt;/p&gt;
&lt;p&gt;The idea is not mine. There are apps that implement shortcut sequences like emacs and its &lt;a href=&quot;https://www.emacswiki.org/emacs/KeySequence&quot;&gt;key sequences&lt;/a&gt;, VSCode&apos;s &lt;a href=&quot;https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-rules&quot;&gt;chords&lt;/a&gt; (which is a bit of misnomer), &lt;a href=&quot;https://zed.dev/docs/key-bindings#keybinding-syntax&quot;&gt;zed&lt;/a&gt;, vim, and logseq to name a few. The advantage is that every shortcut can be semantically meaningful because the amount of possible combinations is infinite. Of course, an absurdly long sequence is problematic as well. The fundamental idea is to create keyboard shortcuts that are easy to remember.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#prototyping-shortcut-sequences-in-a-gnome-extension&quot;&gt;Prototyping shortcut sequences in a GNOME extension&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;GNOME does not natively support shortcut sequences. A shortcut consists of modifier keys and &lt;strong&gt;one&lt;/strong&gt; non-modifier key. Although shortcuts are saved as a string array, the items in the array only serve as alternative triggers for the same action. For example, in GNOME 47 &lt;code&gt;Super Alt Right&lt;/code&gt; and &lt;code&gt;Super PageDown&lt;/code&gt; both switch to the workspace to the right. I am not a linux desktop developer. So I don&apos;t know what would need to happen or which parties would need to be involved to develop this feature properly. Since I am developing GNOME extensions, I wanted to explore the possibility of including shortcut sequences, even if it&apos;s a hacky implementation. At least it could be a feature that is here now. So I&apos;ve created a prototype which does just that. And it works... mostly. I&apos;ll go through some limitations later. But for now, let&apos;s look at the implementation. Let&apos;s start with a high-level overview of the implementation.&lt;/p&gt;
&lt;p&gt;Since shortcuts can only consist of 1 key combination in GNOME, it makes sense to split shortcut sequences into 2 parts. The first part - let&apos;s call it &lt;em&gt;prefix&lt;/em&gt; - will be registered as a normal shortcut with GNOME. When a prefix is activated, the GNOME extension creates a widget that grabs the keyboard input, listens to the subsequent keys, and filters for matching shortcut sequences.&lt;/p&gt;
&lt;p&gt;Let&apos;s go into more detail. In the settings schema XML file, I define keyboard shortcuts as usual but also add additional hidden keys for the prefixes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!-- The following entries are shortcut sequences. This shortcut will be     --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- registered with GNOME shortcut systemThe first item in the array is the --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- prefix. The subsequent items are the key symbols and modifiers          --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- separated by `+` for  each key combinination in a shortcut sequence.    --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- The reasoning behind it follows below the code snippet.                --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; name=&lt;/span&gt;&lt;span&gt;&quot;focus-left&quot;&lt;/span&gt;&lt;span&gt; type=&lt;/span&gt;&lt;span&gt;&quot;as&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[&apos;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;lt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;Super&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;gt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;f&apos;,&apos;97+0&apos;]&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; name=&lt;/span&gt;&lt;span&gt;&quot;focus-right&quot;&lt;/span&gt;&lt;span&gt; type=&lt;/span&gt;&lt;span&gt;&quot;as&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[&apos;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;lt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;Super&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;gt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;f&apos;,&apos;98+0&apos;]&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- This is a non-sequenced aka &apos;normal&apos; shortcut because it&apos;s an array of  --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- length 1. This shortcut will be registered with GNOME&apos;s shortcut system --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; name=&lt;/span&gt;&lt;span&gt;&quot;focus-right&quot;&lt;/span&gt;&lt;span&gt; type=&lt;/span&gt;&lt;span&gt;&quot;as&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[&apos;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;lt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;Super&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;gt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;g&apos;]&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;!-- additional keys for the prefixes of shortcut sequences --&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; name=&lt;/span&gt;&lt;span&gt;&quot;multi-stage-shortcut-activator-0&quot;&lt;/span&gt;&lt;span&gt; type=&lt;/span&gt;&lt;span&gt;&quot;as&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[&apos;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;lt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;Super&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;gt&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;f&apos;]&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; name=&lt;/span&gt;&lt;span&gt;&quot;multi-stage-shortcut-activator-1&quot;&lt;/span&gt;&lt;span&gt; type=&lt;/span&gt;&lt;span&gt;&quot;as&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; name=&lt;/span&gt;&lt;span&gt;&quot;multi-stage-shortcut-activator-2&quot;&lt;/span&gt;&lt;span&gt; type=&lt;/span&gt;&lt;span&gt;&quot;as&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I have a &lt;code&gt;shortcuts.js&lt;/code&gt; file that actually implements the logic on GNOME Shell&apos;s side. The entry for the logic is &lt;code&gt;Shortcuts::register&lt;/code&gt;. You&apos;ll notice that shortcut sequences aren&apos;t registered with GNOME&apos;s native keybinding system because, as mentioned, GNOME doesn&apos;t support the concept of shortcut sequences. So instead of registering the shortcut sequences, I only register the prefixes and create a custom handler, the &lt;code&gt;MultiStageShortcutManager&lt;/code&gt;, for the prefixes. Note that I used the term &lt;em&gt;activator&lt;/em&gt; instead of &lt;em&gt;prefix&lt;/em&gt; in the code. Another thing worth explaining is, starting with the second item, shortcut sequences save the key combinations as key symbols + a modifier state to compare them against the event in &lt;code&gt;MultiStageShortcutManager::vfunc_key_press_event&lt;/code&gt; since I couldn&apos;t find a way to transform the Clutter event into the equivalent &lt;em&gt;accelerator label&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// shortcuts.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Clutter&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    GLib&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    GObject&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Main&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Meta&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    Shell&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    St&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;./dependencies.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; updateMultiStageShortcutActivators&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;../shared.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; Settings&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;./settings.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; Timeouts&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &quot;./timeouts.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/** &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Shortcuts&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; SINGLETON &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; enable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    SINGLETON &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Shortcuts&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; disable&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    SINGLETON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    SINGLETON &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; INVALID_KEY_SEQUENCE_STATUS_LABEL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Invalid shortcut...&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; NO_INPUT_STATUS_LABEL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;No input given...&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; WAITING_FOR_NEXT_KEY_STATUS_LABEL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Waiting for next keys...&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; UNKNOWN_ERROR_STATUS_LABEL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;Unknown error...&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Shortcuts&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /** &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;string[]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _registeredShortcuts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /** &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;MultiStageShortcutManager&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _multiStageShortcutManager &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; MultiStageShortcutManager&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    constructor&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // This keeps the prefixes in sync with the shortcut sequences at least&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        // on `enable` in case the user changed the shortcuts from the outside&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        updateMultiStageShortcutActivators&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getGioObject&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    destroy&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;unwatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_registeredShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;removeKeybinding&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_registeredShortcuts&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; param&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;ShortcutKey&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; param.key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;param.handler&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; - optional only for sequence prefixes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Meta.KeyBindingFlags&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;param.flags&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Shell.ActionMode&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;param.modes&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    register&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        handler &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        flags &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KeyBindingFlags&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IGNORE_AUTOREPEAT&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        modes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Shell&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ActionMode&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NORMAL&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    })&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_registeredShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            throw&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`Shortcut &quot;&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot; is already registered.`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_watchShortcutTypeChange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; flags&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; modes&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_isMultiStageShortcut&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        let&lt;/span&gt;&lt;span&gt; shortcutAddedSuccessfully &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_isMultiStageShortcutPrimaryActivator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            shortcutAddedSuccessfully &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addKeybinding&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getGioObject&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                flags&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                modes&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                ()&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            shortcutAddedSuccessfully &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addKeybinding&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getGioObject&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                flags&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                modes&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;shortcutAddedSuccessfully&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_registeredShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _isMultiStageShortcut&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getGioObject&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;get_strv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;length &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            !&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_isMultiStageShortcutPrimaryActivator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _isMultiStageShortcutPrimaryActivator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;multi-stage-shortcut-activator-\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Meta.KeyBindingFlags&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; flags&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     * &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Shell.ActionMode&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; modes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _watchShortcutTypeChange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; flags&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; modes&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_isMultiStageShortcutPrimaryActivator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;watch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            ()&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // Multi-Stage -&amp;gt; Multi-Stage and Single -&amp;gt; Single shortcut&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // changes don&apos;t need to be handled. The reason for Multi-Stage&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // -&amp;gt; Multi-Stage is that we dynamically fetch the secondary&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // activators from the settings while the primary activator&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // (multi-stage-shortcut-activator-X) is registered just like a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // Single shortcut and thus managed by the native keybinding&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // system. The later part applies to the Single -&amp;gt; Single&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // shortcut changes as well.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                const&lt;/span&gt;&lt;span&gt; multiStageToSingle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    !&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_isMultiStageShortcut&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                const&lt;/span&gt;&lt;span&gt; singleToMultiStage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    !&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;isRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_isMultiStageShortcut&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;multiStageToSingle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;unwatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_multiStageShortcutManager&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;unregister&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; flags&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; modes &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;singleToMultiStage&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;unwatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;wm&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;removeKeybinding&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_registeredShortcuts&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_registeredShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                            (&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; shortcut &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; flags&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; modes &lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            {&lt;/span&gt;&lt;span&gt; tracker&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; MultiStageShortcutManager&lt;/span&gt;&lt;span&gt; extends&lt;/span&gt;&lt;span&gt; Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Actor&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    static&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        GObject&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;registerClass&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /** &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Clutter.GrabState|null&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _grab &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /** &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;Map&amp;lt;string, Function&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;} */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _handlers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Map&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /** &lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;{primaryActivator: string, secondaryActivators: string[], handler: Function}[]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _matchingMultiStageShortcuts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _statusLabel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; St&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Label&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        opacity&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 127&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        y_align&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ActorAlign&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CENTER&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        visible&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    constructor&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        super&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt; reactive&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; visible&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;panel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_leftBox&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        global&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add_child&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    destroy&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_finish&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_handlers&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;destroy&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    start&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcutKey&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_grab&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pushModal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_grab&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_seat_state&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &amp;amp;&lt;/span&gt;&lt;span&gt; Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GrabState&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEYBOARD&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;UNKNOWN_ERROR_STATUS_LABEL&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabelHideTimer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Timeouts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabelHideTimer&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabelHideTimer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; WAITING_FOR_NEXT_KEY_STATUS_LABEL&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;activator&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getGioObject&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;get_strv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcutKey&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_handlers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; scKey&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;shortcutActivator&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt;secondaryActivators&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                Settings&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getGioObject&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;get_strv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scKey&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;shortcutActivator &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; activator&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    handler&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    primaryActivator&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; shortcutActivator&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    secondaryActivators&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_startWaitingForInputTimer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_handlers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; handler&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    unregister&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_handlers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    isRegistered&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_handlers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;has&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;shortcut&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    vfunc_key_press_event&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_startWaitingForInputTimer&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const&lt;/span&gt;&lt;span&gt; eventKeyval &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_key_symbol&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_ignoreKeyval&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventKeyval&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EVENT_STOP&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            i &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            i&lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        )&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; matchingMultiStageShortcut &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; secondaryActivators &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; matchingMultiStageShortcut&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; nextActivator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; secondaryActivators&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;shift&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;nextKeyval&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; nextModifiers&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; nextActivator&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;+&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            const&lt;/span&gt;&lt;span&gt; isMatchingActivator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                nextKeyval &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;eventKeyval&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                // Wayland includes NumLock/fn as part of the state.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                (&lt;/span&gt;&lt;span&gt;nextModifiers &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &quot;0&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_state&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &amp;amp;&lt;/span&gt;&lt;span&gt; ~&lt;/span&gt;&lt;span&gt;Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ModifierType&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MOD2_MASK&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;isMatchingActivator&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;secondaryActivators&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_finish&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    matchingMultiStageShortcut&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;handler&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    return&lt;/span&gt;&lt;span&gt; Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EVENT_STOP&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;splice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;INVALID_KEY_SEQUENCE_STATUS_LABEL&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;EVENT_STOP&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_grab&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Main&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;popModal&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_grab&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_grab&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_matchingMultiStageShortcuts&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_waitingForInputTimer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Timeouts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;remove&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_waitingForInputTimer&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_waitingForInputTimer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabelHideTimer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Timeouts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                interval&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 1500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                fn&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabelHideTimer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hide&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                    return&lt;/span&gt;&lt;span&gt; GLib&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SOURCE_REMOVE&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_statusLabel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hide&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;hide&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _ignoreKeyval&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keyval&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Alt_L&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Alt_R&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Control_L&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Control_R&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Meta_L&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Meta_R&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Shift_L&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Shift_Lock&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Shift_R&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Super_L&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            Clutter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;KEY_Super_R&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        ].&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keyval&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    _startWaitingForInputTimer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_waitingForInputTimer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Timeouts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &quot;shell/shortcuts.js/MultiStageShortcutManager/_startWaitingForInputTimer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            interval&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 3000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            fn&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ()&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_waitingForInputTimer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NO_INPUT_STATUS_LABEL&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                return&lt;/span&gt;&lt;span&gt; GLib&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SOURCE_REMOVE&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; disable&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; enable&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; SINGLETON &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; Shortcuts &lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is also some code on the &lt;code&gt;prefs.js&lt;/code&gt; side that handles the UI, sets the gsettings if the user changed the shortcuts, and keeps the gsettings for the prefixes in sync with the gsettings for the shortcut sequences. However, that is a bit too much to share here and fairly straightforward.&lt;/p&gt;
&lt;p&gt;And that&apos;s it. It wasn&apos;t really hard to implement. That said, the current implementation has some issues, which I am not sure are fixable. But they aren&apos;t &lt;em&gt;that big&lt;/em&gt; of a deal.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#limitations&quot;&gt;Limitations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The main issues are&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Super &amp;lt;NON_MODIFIER_KEY&amp;gt;&lt;/code&gt; as a non-prefix key combination doesn&apos;t register on the initial press. For example, if the shortcut sequence is &lt;code&gt;Super a&lt;/code&gt; ➝ &lt;code&gt;Super a&lt;/code&gt;, and you press &lt;code&gt;Super a&lt;/code&gt;, let go of &lt;code&gt;a&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; the &lt;code&gt;Super&lt;/code&gt; key, and press &lt;code&gt;Super a&lt;/code&gt; again, it won&apos;t work. You need to keep holding &lt;code&gt;Super&lt;/code&gt; after activating the prefix or press &lt;code&gt;Super a&lt;/code&gt; twice after activating the prefix. I believe this is related to &lt;code&gt;Super&lt;/code&gt; being used for the overview and not reaching &lt;code&gt;MutliStageShortcutManager::vfunc_key_press_event&lt;/code&gt; on the first press. If I set the overview key to the right &lt;code&gt;Super&lt;/code&gt; key, it works fine with the left &lt;code&gt;Super&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;Some popups might close when the widget initiates the grab.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Less important issues are&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When opening the shortcut editor in the preference window, I clear all prefix shortcuts so that the shortcut editor can capture the keyboard events, otherwise a keyboard shortcut sequence will be triggered if a prefix is activated. When the shortcut editor closes I re-set the prefixes. This is, at least in theory, problematic if the preference window crashes or is killed, as the prefixes won&apos;t be re-enabled. But that should be a rare case and the prefixes will be re-enabled the next time the preference window is opened or the extension is re-enabled.&lt;/li&gt;
&lt;li&gt;And lastly, since a keyboard shortcut sequence consists of 2 gsettings, things may break if a user manipulates the gsettings directly via CLI or dconf editor instead of going through the preference window where I do the state syncing. I don&apos;t expect this to be big issue though since it&apos;s not a common thing to do.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There might be more issues, but I haven&apos;t found them yet. I will update this post if I do.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Keyboard shortcuts are easier to remember if the keys serve as a mnemonic or if there is a systematic structure to them.&lt;/p&gt;
&lt;p&gt;Traditional shortcuts, triggered by a single combination of at least one modifier key and one non-modifier key, can be hard to remember because there are only a limited number of possible combinations. So if you have many shortcuts, the keys for a shortcut will end up looking random with no relation to the action they will trigger.&lt;/p&gt;
&lt;p&gt;Keyboard shortcut sequences solve this problem. Shortutcut sequences are triggered by a sequence of key combinations rather than just one combination. This increases the amount of possible key combinations for one shortcut to practically infinity. That way the keys for a shortcut can always serve as a mnemonic for the action they perform. Some apps already offer these types of shortcut. Hopefully, in the future more apps or even operating systems support them natively. For now, I experimented with them in a GNOME extension, albeit in a pretty hacky manner.&lt;/p&gt;</content:encoded><category>projects</category></item><item><title>Creating my first npm Package</title><link>https://neverready.app/blog/2024/01-creating-my-first-npm-package/</link><guid isPermaLink="true">https://neverready.app/blog/2024/01-creating-my-first-npm-package/</guid><description>I created my first npm package: create-gnome-extension, a scaffolding tool for GNOME extensions inspired by the web dev ecosystem. The development was fun—testing and publishing, less so.</description><pubDate>Mon, 22 Jul 2024 20:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently at work, I&apos;ve started doing a bit of web development. What surprised me the most, in a pleasant way, was how nice the developer experience was. Everything is fairly straightforward to use, well-documented, and there are a lot of tools available. For instance, Vue and React provide scaffolding tools (&lt;a href=&quot;https://www.npmjs.com/package/create-vue&quot;&gt;create-vue&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/package/create-react-app&quot;&gt;create-react-app&lt;/a&gt;, respectively) that set up your project quickly so you can hit the ground running. I thought to myself &lt;em&gt;Oh, that&apos;s nice, let&apos;s steal be inspired by the idea&lt;/em&gt; and created something similar for my own use case. Enter &lt;a href=&quot;https://github.com/Leleat/create-gnome-extension&quot;&gt;create-gnome-extension&lt;/a&gt;, my first npm package.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-is-create-gnome-extension&quot;&gt;What is create-gnome-extension?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&apos;s first define what GNOME extensions are. GNOME extensions are community written code that modify GNOME Shell, the graphical interface for the &lt;a href=&quot;https://www.gnome.org/&quot;&gt;GNOME&lt;/a&gt; desktop.&lt;/p&gt;
&lt;p&gt;Currently, getting started with developing GNOME extensions can be a bit cumbersome. You either manually create the project structure from scratch or use GNOME Shell&apos;s built-in&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;gnome-extensions&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;command. However, this command doesn&apos;t offer much convenience as it places the files in &lt;code&gt;~/.local/share/gnome-shell/extensions/&lt;/code&gt;, which is the install directory for user extensions, and only creates the mandatory files. Developer tools or other conveniences are not included.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;create-gnome-extension&lt;/code&gt; - similar to other create-X packages - follows a &lt;em&gt;batteries included&lt;/em&gt; philosophy. Here&apos;s a short rundown in case you don&apos;t know how packages like this work. If you run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; create&lt;/span&gt;&lt;span&gt; gnome-extension@latest&lt;/span&gt;&lt;span&gt; # Note the space after `create`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;npm will look up the latest &lt;code&gt;create-gnome-extension&lt;/code&gt; package, temporarily install it, and execute the main &lt;em&gt;bin&lt;/em&gt; defined in the &lt;code&gt;package.json&lt;/code&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. So, &lt;code&gt;create-gnome-extension&lt;/code&gt; is basically just a glorified Node script. When you use it, it will prompt some questions like &lt;em&gt;Do you want to use TypeScript?&lt;/em&gt; and create the project structure for you. Check out the &lt;a href=&quot;https://github.com/Leleat/create-gnome-extension#readme&quot;&gt;README&lt;/a&gt; for more information.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#developing-create-gnome-extension&quot;&gt;Developing create-gnome-extension&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now, I&apos;d like to talk about how it was developing the npm package because there are some things to say. Developing &lt;code&gt;create-gnome-extension&lt;/code&gt; itself was pretty straightforward. It&apos;s just a simple script that copies and writes some files.&lt;/p&gt;
&lt;p&gt;However, I did have a bit of trouble with testing and publishing the package. It was not a great experience.&lt;/p&gt;
&lt;p&gt;The only ways I found to actually test a package are to either publish it on npm or use &lt;code&gt;npm link&lt;/code&gt;. Both aren&apos;t particularly great options. The first option is obviously not ideal, and the second option is just kind of slow. If you know of a better way to test a package, please let me know.&lt;/p&gt;
&lt;p&gt;Another issue I encountered is about how files are included in an npm package. Some files, like the &lt;code&gt;README&lt;/code&gt; or &lt;code&gt;LICENSE&lt;/code&gt;, are always included, while most files need to be manually included. Other files are always excluded with no way to force-include them (like &lt;code&gt;.gitignore&lt;/code&gt;)&lt;sup&gt;&lt;a href=&quot;#user-content-fn-2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. You need to work around it by renaming those files. There is &lt;code&gt;npm pack --dry-run&lt;/code&gt; that will tell you which files will be included in the package but manually checking that dozens or hundreds of files are actually part of the packge doesn&apos;t seem like a good developer experience. In the end, you have to read the documentation on the file patterns carefully to see how each file may be handled to avoid surprises.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#new-project-new-ideas&quot;&gt;New Project, new Ideas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since &lt;code&gt;create-gnome-extension&lt;/code&gt; was a new project, it gave me a chance to try out some new things.&lt;/p&gt;
&lt;p&gt;I switched from ESLint and Prettier to Biome. Biome combines a formatter and linter, so there is one less dependency and fewer config files. It is also faster since it is written in Rust rather than JavaScript. However, the speed advantage is less significant since you likely use linters and formatters integrated into your code editor. That&apos;s where I had issues with Biome. The VSCode extension seems less reliable (as in it stops working sometimes). So, I will probably switch back to ESLint and Prettier once I start working on &lt;em&gt;create-gnome-extension&lt;/em&gt; again.&lt;/p&gt;
&lt;p&gt;I also started using &lt;em&gt;conventional-changelog&lt;/em&gt; and &lt;em&gt;conventional-recommended-bump&lt;/em&gt;, which are based on the &lt;a href=&quot;https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines&quot;&gt;Angular commit convention&lt;/a&gt;, to automatically bump the version number and generate the &lt;code&gt;CHANGELOG&lt;/code&gt;. This works quite well so far. Although, I am unsure about what happens if one type of work needs to be split into multiple commits, e.g., one feature implemented via multiple commits. I believe, if you prefix all commits of a merge request with &lt;em&gt;feat&lt;/em&gt;, each commit would appear in the changelog, which is undesirable.&lt;/p&gt;
&lt;p&gt;Related to the usage of &lt;em&gt;conventional-X&lt;/em&gt;: I experimented with git hooks to ensure that commit messages follow the Angular convention or at least warn if the convention is obviously not adhered to. The idea was to avoid the need to push to a merge request and wait for feedback from the CI. The problem with git hooks is that they are only local to repos and won&apos;t be included when pushed to remotes. So, I put the hooks in a &lt;code&gt;.githooks&lt;/code&gt; directory and added a &lt;code&gt;postinstall&lt;/code&gt; npm script that runs&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; --local&lt;/span&gt;&lt;span&gt; core.hooksPath&lt;/span&gt;&lt;span&gt; .githooks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so that the hooks are automatically set up when you work on the project. However, this led to an error when running &lt;code&gt;npm create gnome-extension@latest&lt;/code&gt; since the package wouldn&apos;t be initialized as a git repo. So, I turned the &lt;code&gt;postinstall&lt;/code&gt; script into an optional &lt;code&gt;hookup&lt;/code&gt; script for now. Maybe using &lt;em&gt;husky&lt;/em&gt; would solve this issue but I didn&apos;t want to add a dependency just for that.&lt;/p&gt;
&lt;p&gt;Finally, I also created some GitHub actions. The &lt;a href=&quot;https://github.com/Leleat/create-gnome-extension/blob/main/.github/workflows/link-and-merge-pr.yml&quot;&gt;most interesting action&lt;/a&gt; appends the URL of the merge request (MR) to all commits before initiating a merge. This allows you to easily open the MR from a terminal on your local machine to look at the discussion that surrounded an MR. This is inspired by the &lt;a href=&quot;https://gitlab.com/marge-org/marge-bot#adding-reviewed-by-tested-and-part-of-to-commit-messages&quot;&gt;Part-of trailer of marge-bot&lt;/a&gt; for GitLab.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#final-thoughts&quot;&gt;Final Thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While there are still some features to implement in &lt;code&gt;create-gnome-extension&lt;/code&gt;, working on it so far was a fun little project. It allowed me to explore new tools and experiment with some other things.&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;&lt;a href=&quot;#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;npm create&lt;/code&gt; is an alias for &lt;code&gt;npm init&lt;/code&gt;. So checkout the documentation for it here: &lt;a href=&quot;https://docs.npmjs.com/cli/v6/commands/npm-init?v=true#description&quot;&gt;https://docs.npmjs.com/cli/v6/commands/npm-init?v=true#description&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true#files&quot;&gt;https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true#files&lt;/a&gt; &lt;a href=&quot;#user-content-fnref-2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content:encoded><category>diary</category></item></channel></rss>