Most of my projects are tools that try to solve something. meowtrics is not. It is a small animated emoji that lives in my system tray and tells me what my machine is doing. It does not page me, log to a database, or integrate with anything. It naps when I am idle and sweats when the CPU is hot. I built it on purpose to be a toy, after a stretch of building too many serious tools in a row, and I was curious how much engineering a toy would actually demand once I took it seriously. The answer turned out to be more than I expected.
The daemon reads vital signs from /proc and /sys with no root required, runs each one through a debounced state machine, and renders an emoji in the tray that reflects the most interesting state at any given moment. Hover gets you a one-line take. Click, on a desktop that supports it, gets you a popup with a per-sensor breakdown. The v0.1 sensors are cpu, ram, swap, thermal, battery, disk, load average, and uptime, and each has 2-5 states. The emoji and the message are picked from a weighted JSON database keyed on the active state. That database is the soul of the project; v0.1 ships 18 sensor categories, 67 states, 220 emoji entries, and 105 message templates, and pull requests against it are the contributions I want most.
Without debouncing, an icon driven by raw sensor readings blinks constantly, and the messages whiplash between states whenever a CPU spike lands. The whole project is unusable in that mode, and the work to make it nice is mostly the work of slowing it down.
Each sensor runs through a state machine with minimum dwell times before it is allowed to transition. The CPU has to be busy for at least N seconds before the icon admits the CPU is busy, and calm for at least M seconds before it admits otherwise. Different sensors get different thresholds because different signals have different natural timescales. Thermal changes slowly. RAM pressure can flip in a second. Swap activity, once it starts, is meaningful immediately. The debouncing is what makes the icon feel alive instead of glitchy, and the thresholds are tuned by use rather than by formula. There is no equation for "pleasant" that does not involve actually living with the thing.
The hardest engineering problem in the project was not the sensors. It was the tray.
Linux desktops have at least three live answers to the question of how to put an icon in the panel. There is StatusNotifierItem, used by KDE, GNOME with the AppIndicator extension, XFCE 4.16 and later, Cinnamon, Budgie, MATE, and LXQt. There is the older XEmbed system tray, mostly dead. There are bar-based custom modules for tiling window managers (waybar, polybar, i3blocks). KDE Plasma 6 also has its own native plasmoid surface that gives a much richer popup if you target it directly.
The shipped approach targets SNI as the lowest common denominator and builds a plasmoid on top for Plasma users. For tiling WMs, the daemon emits JSON via meowtrics json that you wire into your bar config. On KDE you get a real popup, on most other desktops you get the tray icon with a tooltip and a context menu, and on tiling setups you get a text module. The Linux tray ecosystem is treated as solved more often than it actually is, and supporting more than one kind of host environment well is a non-trivial slice of the project.
The daemon is written in Rust. The point of a tray toy is that you forget it exists. If meowtrics shows up in top with a non-trivial percentage, the project has failed regardless of how cute the emoji is. The binary sits at single-digit MB of RSS and well under 1% CPU even with all sensors active, which is the bar I set. Anything that would push it past that (heavy parsing, repeated allocations, a chatty IPC layer) gets cut.
Three layers, lowest precedence first. Bundled with the package at /usr/share/meowtrics/messages.json. A daily auto-refresh from the project Pages site at https://ra-yavuz.github.io/meowtrics/messages.json, so I can ship better lines without a package update. A user override at ~/.config/meowtrics/messages.json, so anyone can rewrite the personality entirely. Templates use {value}, {duration}, and other sensor-specific variables. A CPU at 95% for 12 minutes can pick from a different bucket than a CPU at 95% for 30 seconds. The weighting is per-template, so rare lines stay rare.
The auto-refresh was a small commitment to keep the project alive after release. If I ship a bad line, I want to be able to fix it without asking anyone to upgrade. The user override means that if someone hates my sense of humor, they can replace it wholesale.
Sensor interpretations are heuristic. Hardware varies. The thermal "hot" threshold I use will be wrong on someone's specific machine, and the message that goes with it will be wrong too. The README is loud about this: messages are jokes, not advice. Do not interpret a sweating cat as authoritative system guidance. Whimsy and authority do not mix, and the project is offering the first.
The engineering required to make a toy feel good is not less than the engineering for a serious tool, just differently shaped. Debouncing, multi-DE compatibility, message weighting, refresh policies. None of this was visible in the original sketch, all of it ended up load-bearing.
The temptations I had to extend the project (network throughput, GPU temp, fan speed, idle detection) were almost all already in the v0.2 plan. The fact that I wanted to add them now was a sign I should ship v0.1 first. The test I use for scope versus feature creep is "does this make the existing version better or does it just delay it," and that test served me well here.
The thing I did not expect was that I now glance at the panel a hundred times a day, not for the emoji but for the implicit health check. If the cat is napping, things are calm. If it is sweating, I look at what I am running. It turned into a dashboard by accident, which is a nice outcome for something I built to be a toy.
