log - 2026

Also available as an RSS feed. You can find previous years in the log archive page.

Here’s a log of everything I’ve been up to this year.

may 18 11:51pm

Declarative(ish) KDE Plasma Configuration

switch to KDE Plasma

Being on GNOME, one feature I miss that’s present in basically every other modern DE/compositor is directional focus changing with keyboard shortcuts (as in, Super+h/j/k/l to focus on the window to the left/below/above/right). Additionally, one that I’ve come to appreciate recently is app indicators.

To get these features, I could rely on community extensions. I could also implement and maintain minimal versions of them myself, like I’ve been doing so far. However, today I experimented with declarative KDE Plasma configuration to see how (un)manageable it was. Given that this was the sole reason I switched away from it last month, I figured it was worth at least giving it a try. As a side note, my goal was to configure KDE without home-manager, so plasma-manager was out of the question.

Configuring the settings I cared about declaratively in KDE Plasma was just about what I expected: some settings respected system-wide configuration in /etc/xdg, some didn’t and required workarounds, and some files (like kwinoutputconfig.json for monitor configs) were better off being persisted because of their stateful and machine-specific nature.

Overall, I was satisfied with what I came up with and switched back to KDE Plasma; it supports all the features I could ever ask for natively, and it’s been functionally stable and reliable in recent years. Whether the config files will be a pain to maintain over time is to be determined.

apr 25 11:25pm

Changing the default terminal in GNOME: not that straightforward

related issue

A big reason why I reach for desktop environments these days is because they ship with a suite of all the desktop programs you’d expect that are designed to integrate well with each other. In other words, they already come with a curated set of GUI programs, so I don’t have to think about it.

Occasionally, I find myself needing to open a terminal in a directory I’m viewing in a graphical file browser. Most file browsers I’ve used make this easy by adding an entry for exactly that in the right-click context menu. By default, Nautilus opens the directory in Gnome Console. Since I found myself doing this a few times today, I went ahead and tried changing the default to Ghostty, my preferred terminal.

It turns out that, due to a long-standing limitation of GLib, this isn’t configurable—neither through the settings GUI, nor through modifications to dconf directly. The generally accepted approach to getting this feature is using a Nautilus extension like nautilus-open-any-terminal. While the extension is very well maintained, I’ll hold off on adding it to my setup because I’m rarely in this situation.

apr 24 10:38pm

Created a /uses page

add “uses” page

Spoiler: I use the things I use because I enjoy using them.

apr 23 11:12pm

/uses pages are neat

I like developer tooling. I like discovering new tools that could improve/streamline my professional and hobby workflows, and I especially like getting the most out of the tools I already use by learning about and mastering their features. Over the years, I’ve found that the one thing that scratches this (typically recreational) itch is snooping around other people’s dotfiles. In fact, my main method of discovering new features in Neovim is looking at the recent commits of core maintainers’ dotfiles every now and then, many of which dogfood new and prototype future features right in their own configs.

As I slowly familiarize myself with the wild world of personal websites, a page I’d often see is /uses, which I’ve come to think of as human-readable representations of their dotfiles, often spanning topics outside of software and tooling. I especially like the pages that go beyond listing the contents of their toolbox by providing short blurbs of how, why and where each item fits into their workflows. As a bonus, they also give you a glimpse of the person’s habits and personality in ways that dotfiles can’t.

Today I asked myself the curious question: “Why don’t I have one on this site?” I really enjoy looking through these, so naturally, I should join in and make one for myself. I’m personally curious to see how my toolset would look laid out in this format. Maybe it’ll even answer a question or two for anyone passing by (^-^)ノ

Let’s get to it!

apr 22 11:38pm

A small yet important fix to my NixOS auto-updater

auto-update fix

Unsurprisingly, my NixOS auto-update service isn’t quite perfect yet.

Late last night I noticed my auto-update service wasn’t even attempting to update the system flake when it definitely should’ve been. For context, I have the service set up to update all flake inputs either when it’s Saturday or when it’s been longer than 7 days since the last flake update (i.e., overdue). Since the attempts from last Saturday failed, it definitely should’ve been attempting again that night. What was going on?

The problem was that the service was simply taking the timestamp of the last commit for overdue checks. In this case, the last commit at the time was two days prior, and definitely wasn’t a flake update commit.

The fix was simple: take the timestamp of the last commit that modified flake.lock. While there are flake.lock-modifying config changes such as adding/removing inputs, which would set the “last-updated” timestamp to that commit, I’m very careful with those and I don’t expect to do many of them going forward. Even then, the system would unconditionally update anyway the following Saturday.

I’m glad to have run into this case this early on, since I’m pretty frequently making changes to my configuration as I learn more about Nix, NixOS, and the huge ecosystem around it.

apr 21 3:35pm

Massive day for existing Framework Laptop 13 owners

Today Framework announced the Laptop 13 Pro, their first major hardware revision.

As I watched the reveal event, several questions persisted in the back of my mind: what does this mean for existing 13 owners like me? Will I have to purchase an entirely new laptop to get in on these hardware revisions? Surely they haven’t abandoned their core value of upgradability in releasing this, right? Although they did mention the ability to upgrade original models piece-by-piece, the extent of this claim wasn’t clear to me. As it turns out, they have in fact thought of us, making it abundantly clear in a demonstration video that every original 13 owner has an upgrade path.

The main reason I decided to purchase a Framework Laptop 13 was first-class Linux support on modern hardware; all the other great features were nice-to-haves, and I was willing to sacrifice a few things that I liked about MacBooks: their CNC-machined chassis, their unmatched haptic trackpads, and their industry-leading battery life. It looks like their chassis upgrade kit might just make my laptop a strict upgrade[1] from MacBooks for my use case! I’m looking forward to upgrading my machine when it becomes available.

[1] It sounds like the main factor driving the Pro’s massive leap in battery life is the new Intel boards, which I’m not looking to purchase, but a bump in battery capacity is still a win in my eyes ദ്ദി・ᴗ・)

apr 20 11:29pm

Successfully shielded from broken updates

I didn’t get around to much today, but I noticed that my last scheduled unattended update on Saturday failed, and it was related to the kernel. While spooky, it was just because of a crypto kernel module being renamed (issue). The fix has already been merged, and it’s been backported to the stable branch. The silver lining is that I’m able to witness my unattended upgrade implementation handle this gracefully: every day since Saturday, it’s been attempting a flake update a few times before backing off and trying again the next day. In the next coming days, the patch will land in stable, the service will successfully update the flake, and it’ll update my system without me having to intervene. Neat!

apr 19 3:07pm

Not switching to COSMIC Desktop

After using COSMIC Desktop for a short while, I decided not to switch just yet:

  • The rapid development pace doesn’t mesh well with versioned OS releases. Since it’s a relatively young project, important features are in the works, and bugs are constantly being ironed out. Since I intend to stay on NixOS’s stable branch, I could potentially be stuck with said bugs or without said features until the next major release.

  • The bugs reported in the tracking issue for various games tend towards imprecise cursors and broken/finicky full-screen, which sound pretty game-breaking.

  • No system-wide configuration (from what I could find), and configuration is spread across many files. These aren’t huge problems, but the combination of the two makes it less straightforward to configure declaratively than my current go-tos (GNOME and Sway).

Obviously, these issues will disappear over time: the feature set will mature, the bugs I care about will be resolved, and Nix configuration tools are bound to surface as the project gains more traction. I’m looking forward to trying out future releases, but for now I’ll stick with what I have.

apr 18 10:30pm

Trying out COSMIC Desktop

Today I tried out COSMIC Desktop for the first time. Luckily, setting up impermanence on my NixOS systems made it easy for me to quickly test drive it on bare metal alongside my current DE without rebooting, and wipe it and its configurations completely from my system after I’m done.

What I like:

  • Full-fledged desktop environment with tiling window management as a core feature. Huge! While I don’t strictly require it, most of my configuration in DEs involve setting TWM-like keymaps, which are defaults in COSMIC Desktop.

What I miss:

  • “Scratchpad” windows. A huge component of my workflow is being able to toggle the visibility of a terminal window with a keymap. This is a highly requested feature, and it looks like it’s being considered for Epoch 2.

  • Auto-hiding the top panel. I like to keep panels around for app indicators and for quickly checking the time, but I prefer them to be hidden by default and toggleable through a keymap. It turns out that it’s been acknowledged and desired by a contributor, so there’s potential for it coming in the future.

  • Centering a floating window. Having this bound to a keymap is nice for when I’m only dealing with a single window that doesn’t need to take up the full screen.

What would be a deal-breaker:

  • Broken screen capture. I don’t need this too often, but when I need it, I need it.
  • Bluetooth/wifi connectivity issues related to the DE
  • Issues with Steam games I play (tracking issue for various games)
  • Tricky declarative configuration

Scratchpads and auto-hiding panels aren’t features in GNOME either, but I was able to create simple extensions that implement them. I’ll have to see if I can do something similar for COSMIC Desktop. Either way, I’ll use it for a bit longer to see if any of these deal-breakers apply. For now, I’ll keep it installed alongside my current DE.

apr 17 10:15pm

Planning forneus’ memory map

Today I looked a bit into the Game Boy’s memory map. It’s addressed via a 16-bit address bus and is laid out like this:

0x0000 to 0x7FFF: 32 KiB of cartridge memory (ROM)
0x8000 to 0x9FFF: 8 KiB of video RAM
0xA000 to 0xBFFF: 8 KiB of switchable memory from the cartridge
0xC000 to 0xDFFF: 8 KiB of work RAM
0xE000 to 0xFDFF: echo RAM
0xFE00 to 0xFE9F: OAM
0xFEA0 to 0xFEFF: unusable
0xFF00 to 0xFF7F: I/O registers
0xFF80 to 0xFFFE: high RAM
0xFFFF: interrupt enable register

While I won’t be implementing the features of all of these regions as I work on the CPU core, I’ll at least model them and their access patterns in code.

resources: Game Boy system specifications, Memory Map

apr 16 10:09pm

Updated to Zig 0.16.0

genpass updated, lumectl updated, mlv updated, forneus updated

My second pass at updating my zig projects after reading all of the release notes was pretty uneventful (which is a good thing). There was only one deprecated feature to migrate from: @cImport to tranlate-c. The method of C translation is transitioning from being a language feature to being a dedicated step in the build system. The @cImport/@cInclude syntax always looked a bit alien to me, and handling all this in the build system makes logical sense.

As a side note: one change I found to be an unexpected yet pleasant surprise was package dependencies now being fetched into a project-local zig-pkg directory. This immediately came in handy yesterday while updating lumectl and its libusb dependency to 0.16.0, where I was easily able to make hotfixes to the local copy of the dependency along the way. What I initially figured was a hacky exploit of this new change turned out to be its exact use case!

apr 15 11:13pm

Updating to Zig 0.16.0

genpass update, lumectl update, mlv update, forneus update

Zig 0.16.0 has officially released, and I’ve almost completed updating my projects. Overall, the process didn’t end up being too much work. In some cases, I ended up deleting more code than I added thanks to juicy main.

All projects pass tests and work as before, but I’ll wait a bit longer before merging the updates since I haven’t read through the release notes yet; I’ve only read bits and pieces as needed while following compile errors. I’ll be looking out for new features to use that apply to my projects, and soon-to-be-deprecated APIs that’d be a good idea to migrate from.

Along the way, I also submitted a pull request to the zig port of libusb, a dependency of lumectl.

apr 14 8:37pm

Definitely making a Game Boy emulator from scratch

initial CPU work

It’s happening (;⌣̀_⌣́)

After gaining a solid understanding of the CPU core by reading a bit of the GB: Complete Technical Reference, I modelled its components and implemented a few machine instructions.

There’s probably a lot wrong with my initial pass at things, but I’ve done enough to convince myself that this is worth doing.

Next step: learn about and implement the SoC’s memory map and memory access.

apr 13 8:38pm

(Probably) making a Game Boy emulator from scratch

I’ve decided to at least consider making a Game Boy emulator from scratch.

Here are my high-level specs and constraints for the project so far:

  • at minimum, support Linux and web (WASM)
  • minimize dependencies
  • avoid looking at similar projects for reference

Initial steps:

  • do some precursory research to get a feel for what I’m getting myself into
  • figure out a suitable component of the system to start on (probably the CPU)
  • figure out how to test my implementations
  • set up a dev environment with a tight feedback loop

This’ll be fun!

apr 12 9:49pm

Writing WebAssembly by hand

wasm-wat

I’ve used WebAssembly modules in JavaScript, I’ve compiled software projects with WebAssembly as the target platform, but I’ve never written WebAssembly by hand. Today I changed that. Here are some of the things I explored today:

  • WebAssembly text format (.wat), writing simple functions with basic control flow
  • using wabt to translate .wat to .wasm and back again
  • WASM and JavaScript interoperability: creating a JavaScript module containing functions for use in WebAssembly, and exporting WebAssembly functions for use in JavaScript
apr 11 10:55pm

A clean slate every boot

add impermanence

It’s been done! I’ve successfully integrated the the “impermanence” module into my NixOS configuration and deployed it to one of my machines. Aside from the tedium of figuring out which files and directories to persist, it was a pretty smooth process.

The main benefit that drew me towards opt-in state is the mitigation of every sysadmin’s worst nightmare: configuration drift. By wiping the system clean and declaring a focused set of files worth persisting, packages and their configurations are always in their default states. This mitigates the possibility of package updates requiring manual intervention, and post-update breakage due to obsolete configuration. On the other side, if these things ever do happen, I’d have a precise list of every configuration file I’ve chosen to deviate from upstream, so I’d know exactly where to look.

As planned, I also swapped KDE Plasma for GNOME, bringing over my dconf configuration and my two single-feature plugins that I threw together some time ago. While maintaining extensions is admittedly at odds with my goal towards “zero-maintenance” Linux, their minimal nature has kept them working with zero code changes since GNOME 46.

apr 10 11:33pm

Considering NixOS systems with opt-in state

Around the time I committed to using Linux full-time, I came across this article that discusses a solution for an immutable Linux system, and the “impermanence” module that implements it. I was immediately intrigued, but felt that NixOS wasn’t for me, so I never delved further.

Today I happened to come across the impermanence module again, and now’s as good a time as any to try it out since I’ve transitioned all my machines to NixOS. Here’s the high-level plan so far:

  • use the btrfs method instead of the tmpfs method to wipe root on boot to avoid the latter’s limitations
  • do not persist all of /home: opt into state for individual files and directories to some reasonable extent
  • consider switching from KDE Plasma to GNOME. I don’t configure my DEs much to begin with, and declarative dconf configuration is more straightforward and better supported than managing KDE’s ~/.config files.
apr 09 9:31pm

Sokol key inputs and web builds

handle inputs, web builds

I set up basic input handling in my sokol test project, and tried out web builds by following an upstream example. It turns out that sokol-zig’s build steps for wasm32-emscripten use emsdk directly, which vendors its own dependencies that assume a standard dynamic linker. In other words: it’s incompatible with NixOS.

While the build likely would’ve worked if I enabled nix-ld on my system, I instead opted for handling web builds in an OCI container, which worked as expected. I’ll probably end up enabling nix-ld on my systems down the line anyways, but I’ll wait for a reason more substantial than a throwaway test project.

apr 08 11:40pm

Installing my dotfiles with NixOS

installing dotfiles

I had the idea of having NixOS deploy my dotfiles to my system without the use of home-manager, and while keeping my dotfiles repo as a separate non-nix repo. The idea was to somehow use my non-flake dotfiles repo as an input to my system flake, and to install the configs and scripts system-wide.

Luckily, using a non-flake repository as a flake input was as simple as setting flake = false. From there it was a matter of placing the dotfiles in the right places, with a few caveats:

  • jj as well as ghostty (for now) don’t have support for system-wide configuration files, so I installed them as symlinks in their user config locations.
  • I initially tried wrapping jj and ghostty with symlinkJoin + wrapProgram to attach --config options to their binaries, but jj doesn’t support aliases in configs loaded with --config, and the resulting .desktop file for ghostty still pointed to the unwrapped binary, and patching it was a bit too hacky for me.
apr 07 10:58pm

Sokol + Zig + Nix

sokol setup

I spent a bit of time setting up a dev environment for sokol, and ran into a hiccup along the way: zig’s linker wasn’t able to find the system dependencies in the nix dev shell:

error: fatal linker error: unhandled relocation type R_X86_64_JUMP_SLOT at offset 0x8bf28
note: in .zig-cache/o/4a032a8a120ffe6d634b024b82dc6104/libsokol_clib.a(/nix/store/39fgb5pr1r6k3szvfhpfmv9jp829kisx-libglvnd-1.7.0/lib/libGL.so/):.got

I found two solutions that worked: using the LLVM backend unconditionally, and explicitly linking the dependencies in the build script. I went for the latter approach. Although the LLVM solution is cleaner, going for it would mean giving up the super quick debug builds that the self-hosted x86 backend provides.

apr 06 7:19pm

Streamlining the log entry process

log script

On a whim, I’ve made it a goal to update this log page daily. If there’s any hope of me doing this longer than a few days, it’s if I make it as easy as possible to create and deploy a new entry. This script automates the entire process, and works regardless of the current working directory.

I now have zero excuses. We’ll see how long I can keep this up (>؂ •́)ᕗ

apr 05 11:06pm

A peek into Zig 0.16

zig-async-io

I decided to briefly try out zig’s new async I/O primitives ahead of the version 0.16 release, and they do in fact do what they say on the tin! The ability to express asynchrony in zig like you would in higher-level languages is very exciting.

I also took this opportunity to play around with the juicy main feature, which provides the main function resources that the typical zig program would want access to at runtime. This cuts down on a considerable amount of boilerplate starter code, which lowers the barrier of entry for starting new projects, and for new users looking to pick up zig.

I’m looking forward to this and future releases of my favourite programming language :) .

apr 04 9:57pm

First Entry

This is my first log entry!

Last year I migrated this site to Zine to play around with and hack on the project. I ported everything over (from Zola) without too much trouble; the starter template got me 99% of the way there because of the site’s bare-bones nature. Since the work’s already been done, I decided to commit to using Zine without much thought, and forgot about it.

Setting up this log page today is when I got to experience where Zine shines. Here’s a list of things that stood out to me:

  • how expressive yet concise Scripty is
  • the function chaining approach to expressing intent: $page.date.format('2006').eql($build.generated.format('2006'))
  • the explanations of design decisions interlaced throughout the docs
  • the emphasis on developer UX, with the SuperHTML language server being a core component of this project

My verdict: I’m really enjoying it so far!