DEV Community

Dayvster 🌊
Dayvster 🌊

Posted on Originally published at dayvster.com

zenkai: The App Launcher I Wrote Because I Wanted Something Fast and Beautiful

If you only came here to try it, grab it at zenkai-launcher.netlify.app and the source is on GitHub. The rest of this is about why it ended up built the way it is.

Every few months I install another app launcher, convince myself this is the one, and then about a week or so later I uninstall it because something small irritates me or it cannot do what I want it to do. After repeating this process about a dozen times, I finally gave up looking and wrote my own.

I have always struggled with naming things and this project was no exception. After an embarrassingly long time I settled on Zenkai, which comes from my childhood obsession, Dragon Ball. "Zenkai" is a massive power up that a Saiyan obtains after a difficult battle, what a perfect name, because that is exactly the vision I had for this project. I wanted it stronger, faster and more performant after every single iteration. I wanted to dive deep into performance optimisation rabbit holes where I spend hours on something that maybe shaves off 20ms or so. I wanted this to be a part time hobby.

I also wanted it to be cross-platform, because I use Linux and as any good Arch Linux user (BTW!!) I have to have a customizable performant launcher. For work I use macOS and while the native launcher there is great, I wanted to have the same experience on both systems. Windows I will be honest, I only occasionally use when I have to, so I would be lying if I said it was a high priority. Plus, Windows is pretty challenging to make a launcher for, but more on that later!

I got all 3 systems working and fully supported.

I also wanted it to be fast and beautiful, because I wanted to use it every single day and I wanted to enjoy using it. I wanted to be able to press a hotkey, type two letters, and have the thing I wanted already highlighted, hit enter, and have the launcher gone before I had even registered that it was ever there.

That is the whole idea, and so I started building it. The first choice I had to make was Language and GUI framework. I chose Zig and Qt6!

Zenkai Uses QT and Zig

I have been developing in Zig for a while now. I love the language, I love the simplicity that you can manually scale into complexity if you want to. Zig feels like C, it looks like C, but with sane memory management tools, but without a compiler that throws a hissy fit if you do not handle every single potential memory issue under the sun. It trusts you to be a craftsman, and it trusts you to know what you are doing. It is a language that does not get in your way, and it is a language that I can trust to be fast and efficient.

It is as if someone handed you a well-sorted professional toolbox.

Qt I did not choose on principle, I chose because I have used it before. I have shipped Qt applications professionally, I like its widget set, I like that you can make it look genuinely beautiful instead of the usual grey business-tool look that everything ends up as, and it is cross-platform. Zig I chose because I love it and it fits how I think, manual memory with sane defaults and a standard library that actually helps. So that is the entire strategic decision and it is not a particularly clever one. I picked the two things I already loved and hoped the rest would sort itself out.

Zenkai Is Fast

I have been obsessed with performance for as long as I can remember. Usually there is no room for it, because whatever you are working on belongs to somebody else or somebody is eagerly waiting for it, there is a deadline, and spending a week shaving 40ms off a startup path makes you a difficult person to work with. zenkai is mine and more than that I took on the project as a hobby and not a clear goal or product that is supposed to make me money. Which means nobody is waiting on it and nothing is going to break if I spend three days on something that makes it 15ms faster. For the first time in forever I could indulge my performance obsession properly, and it felt liberating.

$ zenkai --debug --quit-when-shown
[zenkai] window fully visible in 140ms
Enter fullscreen mode Exit fullscreen mode

So anything under 200ms is percieved as instantaneous to a human, in zenkai's case I got it down to 140ms on my machine with plugins loaded, 100-200 apps parsed and icons loaded and rendered.

But it gets even faster, if we run it without plugins and the no-icons flag we get 20-70ms until fully interactive. That is frankly ridiculous considering it's a non native GUI application built with Qt and Zig, and it is a testament to the power of both.

What was even more shocking was how little of that startup time was mine. Breaking the startup down put everything I actually wrote at 14.75ms-20ms of the 140, and the rest was Qt building widgets and the compositor putting pixels on the screen.

All of this is very brief and I will provide a more detailed breakdown of the performance in a future post, because just that alone is a topic that deserves a longer form content type post to explain fully how I got there and what techniques I used to get there. But for now, just know that it is fast, and it is fast because I wanted it to be.

Zenkai Is Beautiful

There is this false dichotomy that you can either have a beautiful product or a fast one. I have always resented that idea to an extent. Because it is clearly not true. We do not have to look any further than Italian supercars, which are beautiful and fast. With enough time, love for the craft and dedication, you can have a product that is fast, well made and beautiful. It is not easy, but it is possible.

That is a large part of why I made this.

A launcher that looks beautiful but takes four seconds to open is not a beautiful launcher. Its slowness is part of the experience. And a launcher that starts instantly but looks like a settings dialog is not a great launcher either.

I wanted both.

Because to me, a product isn't truly beautiful unless it feels good to use, and it isn't truly fast if speed is the only thing it has going for it.

The goal was to make zenkai look good the first time you run it, before you have configured a single thing. No config file, no wiki page, no spending half an hour tweaking colors just to make it presentable. Install it, launch it, and it should already look like something somebody cared about.

But good defaults should not get in the way of making it yours. Changing the entire look of zenkai is just a CLI flag away, so it can fit into whatever setup you already have without turning customization into a project of its own.

zenkai in the launchpad theme, a full screen grid of application icons in the style of the macOS launchpad


launchpad, which is the one I spent the most time on and the least sure about

The other half of that goal is fitting in. Most people who care enough to rice their desktop have already picked a colour scheme, usually from the list everybody uses, and they do not want to pick a second one for their launcher. So all of the popular ones are in there, gruvbox, dracula, ayu, catppuccin in all four flavours, tokyo night in its four variants, nord, rose pine, kanagawa, everforest, noctis, solarized, monokai, one dark, github, and plenty more.

zenkai in the ayu dark theme, near black background with muted orange selection highlight


ayu-dark

zenkai in the dracula theme, dark purple background with a bright purple selection highlight


dracula

zenkai in the gruvbox theme, warm dark background with orange selection highlight


gruvbox

There is a screenshot of every single one of them in the screenshots directory on GitHub, if you want to pick before you install rather than after.

That works because Qt styles a widget set through QSS, which is Qt’s stylesheet format, and a theme in zenkai is a data file rather than code. That is the whole reason there are 65 of them and the reason adding another one is not work. You are not writing a new screen, you are filling in colours.

zenkai in the retro theme, amber text on a near black background


retro, which is amber on black and is not a colour scheme anybody ships

I also did some creative ones that are not really anybody’s scheme. And the one I am genuinely proud of is the native Windows theme, because it reads your accent colour and themes itself around it, so on a machine where you have already told Windows what colour you like, zenkai just quietly agrees with you.

Then there are the two high-contrast themes, and those exist for one reason only, which is accessibility. Pure black on pure white, bold everywhere, no greys at all, and every state inverts completely instead of tinting.

zenkai in the high contrast theme, pure black background with bold white text and a white selection highlight


high-contrast

Most schemes spend their greys on the dimmed text and the inactive borders, and greys are exactly what disappears on a bad screen or with low vision. Not being able to see which row is selected every single time you open a launcher is a real problem, so these two throw the greys out entirely. There is a dark and a light version and nothing left to tune.

Zenkai Is Yours Too

A theme gets you most of the way there, and then there is the stuff a theme cannot do.

Some of that is just flags. --menu is the one I use most, because it lets me put my own entries into the launcher without writing anything. --menu=gh|GitHub|github-icon and suddenly a search for gh opens a browser tab instead of starting an application. There are flags for the icon size, the window size, which monitor it opens on, whether the animations run at all. It is a launcher that assumes you want to bend it, which is not an assumption most of them make.

The rest is Lua, through ziglua, so themes and actions can be extended without rebuilding. That was supposed to be the small feature. plugins.zig is now 26KB where I expected a fraction of that, which is what happened the one time I said it would be small.

The part that took the most work was the sandbox, because embedding a scripting language in a GUI application turns out to need three separate things and I only know the list because I got the first two wrong. Capabilities get removed rather than policed, so there is no permission list for a plugin author to misconfigure. And there is an instruction limit, because a plugin stuck in an accidental infinite loop hard hangs the whole window, since the Qt event loop is sitting on the other side of that call and never gets a chance to run again. Errors go to the log instead of stdout, attributed to the plugin that caused them.

That list deserves its own post, so I am not going to stretch it here.

What I Want It To Become

The honest version is that this started as a launcher and keeps turning into something else, and I am fine with that.

The biggest thing I want is more capabilities. Right now it launches applications and runs things, which is a launcher. What I actually want is closer to what Vicinae and Raycast do, which is a command palette with genuinely useful commands beyond starting processes. Clipboard history, quick calculations, scripts, window and workspace control, that whole category. I use a launcher every single day and the fastest thing on my keyboard right now is starting applications, which is a waste of a good global hotkey. And I do not mean that as a complaint, because the thing already keeps a scorecard of what I actually use it for, so at that point it might as well be funny about it.

I also want a Hyprland commander mode, which is a slightly more niche thing but I use Hyprland and I want zenkai to be able to drive it properly rather than being an application launcher I happen to also run on that machine.

And then there is the part that is less fun to admit, which is technical debt and performance. A few months in, this has left some genuinely ugly corners that I know about and have been choosing not to look at, in the way you do not open a file you know is going to cost you an afternoon when you are having a good day. Technical debt is a bad joint, you can get away with one and you can get away with a few if you are careful about which ones, but eventually the whole thing comes apart in your hands and you find out which ones mattered all along. I want to clean that up properly. And I want it faster still, because 140ms is a good number and there is no reason it should be the number I stop at.

Build Something

None of the parts I am actually proud of were the plan. The lazy icons came from realising I was paying for a hundred rows nobody would see, the deferred scan came from wanting the window up before anything heavy started, and the whole thing got fast because I was stubborn about a number in a way I never get to be at work.

So if you have been uninstalling launchers looking for one, or if you have looked at Zig and wondered whether it is any use for anything graphical, this is my answer to both. Go build the thing you kept looking for, even if the only reason it does not exist yet is that nobody bothered.

Going Deeper

Time for some more honesty, this post was originally A LOT longer, I had to trim it down to something more reasonable to not bore most of you. Striking a good balance between technical detail and readability is hard, and I am not sure I got it right. So I am going to write at least 3 more posts where I go into the details of how I got it to be fast, how I got it to be beautiful, and how I got it to be cross-platform. The first one will be about performance, because that is the part that took me the longest to understand and the part that I am most proud of.

What I have written here is the short version. There is a lot underneath it and I want to go through it properly, because the interesting parts are the ones that took me the longest to understand and I have mostly just said they work.

So that is what is coming. Getting the whole thing under 200ms, including the scan of every application on the machine, and what happens in the sub 50ms cases when there is nothing left to skip. How it actually beat the Windows Run utility, which is a native dialog with no Qt runtime to load, and why a Qt application in Zig winning that was never really in doubt once I understood where the time was going. How I got Windows application discovery working at all, given that Windows describes the same application in five different places and some applications only exist in one of them, and why the honest answer there involved 148 lines of C++ in an otherwise pure Zig codebase.

If you want the reasoning rather than the result, those are the ones worth reading.

Try It

Check it out at zenkai-launcher.netlify.app, the source is on GitHub.

Give it a go. If you like it, drop the launcher a star on github or just a hey wassup, I like your launcher on Twitter, I will genuinely appreciate it.

Thank you for your time and patience reading this article

Top comments (2)

Collapse
 
jacobfoster21 profile image
jacob foster •

This is a great example of building something because existing tools never quite fit the way you work. I especially like the focus on speed and consistency across Linux and macOS. The 20ms optimization rabbit holes are very relatable too. Zenkai is a fitting name for a project that keeps getting better with every iteration.

Collapse
 
kyisaiah47 profile image
kyisaiah47 •

Does the startup split change much when you measure the first accepted keystroke rather than the window becoming visible?