- Kotlin 77.9%
- C 21.6%
- Makefile 0.4%
- Shell 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| contrib | ||
| etc/pam.d | ||
| lib | ||
| media | ||
| src | ||
| .SRCINFO | ||
| LICENSE | ||
| Makefile | ||
| PKGBUILD | ||
| README.md | ||
klear
this is a hyprland status bar i made in kotlin/native. i started it because i tried a bunch of the existing bars and none of them felt right, so i figured how hard could it be (it was pretty hard). it draws everything with nanovg + opengl straight onto a wlr-layer-shell surface. no jvm, no gradle, no electron — just the binary.
i learned a ton building this, so if some of it looks over-engineered, that's me figuring things out in real time. it works on my machine and a few friends' machines, which i'm pretty proud of.
what it does
- session lockscreen (
klear -L) — it's anext-session-lock-v1client with real pam auth. it grabs a screenshot, blurs it, and uses that as the backdrop, with a themable card and a refraction halo around the box - workspace pill with a sliding indicator — works on hyprland, sway, niri, or mangowc, and it figures out which one you're running on its own
- clock pill (uses a real font, you pick the strftime format)
- click the clock and you get a calendar popup — month navigation, events pulled from
.icsfeeds (local files or urls), and alarms you can set - system tray — i had to write the whole
StatusNotifierWatcher+StatusNotifierItemthing by hand over dbus. there's no library for it, so i spent a lot of time staring at the spec - right-click any tray icon → popup menu, parsed out of
com.canonical.dbusmenu - battery pill on the far right with a tooltip + a power-profile picker. you can hide/show it at runtime over an ipc socket
- the bar can sit on any edge — top, bottom, left, right. vertical bars rotate the widgets but keep the text upright (that took a few tries to get right)
- "pills" mode where each widget gets its own glass chrome, or "unified" mode where one bar-wide pill sits behind everything
- the glassy refractive look — rim refraction + chromatic aberration done through a hyprland
screen_shader, plus a highlight that follows the sun angle so it tracks time of day - klearglass blur sets itself up — klear runs the
layerrule = blur, klear-baritself on startup, so you don't have to touchhyprland.conf - app launcher (Super opens it by default), fuzzy-matched against your
.desktopfiles + flatpak apps - control-center sidebar — wi-fi (NetworkManager or iwd), bluetooth, audio (pipewire via wpctl), and notification history. saved networks and paired devices get inline forget chips, both in the sidebar and the settings panel
- the wi-fi connect flow lives inline in the settings panel: click an unknown network and a password prompt appears under the row, click a saved one to reconnect, or hit Forget. WPA-Enterprise / 802.1X (the eduroam kind) also gets a username field
- every popup animates open and closed (power profiles, tray menus, sidebar, launcher, settings, OSDs), and the screen-shader refraction follows the scale + slide every frame
- multi-monitor: one bar per
wl_output, each with its own workspace pill that animates independently. overlays (launcher/sidebar/settings/popups) follow whichever monitor is focused, and there's a keyboard-layout pill next to the clock klear magnifiertoggles a loupe above the cursor — convex-lens magnification with a chromatic rim and a bright outline, also done as ascreen_shaderpass- in-app settings panel (the gear button) for wallpapers, theme tweaks, network, etc.
- desktop notifications via
org.freedesktop.Notifications— a banner stack that pops up plus persistent history in the sidebar - bottom popup (
klear bottom) with cpu/mem, an mpris now-playing disc, sun position, weather, and a cava-style audio spectrum - OSD popups for volume, brightness, mic mute, charger, and caps lock
- the whole theme dumps to
~/.config/klear/theme.jsonon first run, and hot-reloads when you edit it
screenshots
top edge, pill style (default):
top edge, unified style — one bar-wide pill behind every widget:
bottom edge:
left and right edges — vertical bar, widgets rotated, text upright:
app launcher (klear launch / Super):
control-center sidebar (klear sidebar) — wi-fi, bluetooth, audio, notifications:
deps
system packages you'll need:
wayland-client,wayland-egl,EGL,GLwayland-scanner,wayland-protocols(only needed to build)- a TTF font — it looks for
/usr/share/fonts/Adwaita/AdwaitaSans-Regular.ttfby default, but you can pointclockFontPath/menuFontPathat whatever you have
build tools:
kotlinc-nativeandcinterop(these come with the kotlin/native toolchain)gcc
optional runtime stuff — klear runs fine without these, the matching feature just gets dimmed or hidden:
matugen— wallpaper-driven themingnmclioriwctl— wi-fi controls in the sidebarbluetoothctl— bluetooth controlswpctl(pipewire) — audio + the volume osdpowerprofilesctl— the profile picker in the battery popupswww,hyprpaper,swaybg,wpaperd,mpvpaper, orfeh— any one of these works as a wallpaper backend
build
make deps # one-time: clones nanovg into lib/nanovg
make # builds bin/klear
after that, rebuilds are just make. make clean wipes the artifacts if something gets weird.
the individual targets, if you need them:
make cinterop— regeneratelib/{nanovg,wayland}.klibmake c-objects— recompile the C side (nanovg + bridge + protocols)make regen-xdg— rerun wayland-scanner against the system xdg-shell xmlmake run— build and launch
install
sudo make install # → /usr/bin/klear
sudo make PREFIX=/usr/local install # somewhere else
make DESTDIR=./pkg install # staged install (for packaging)
arch / aur
klear is on the aur, so paru -S klear should do it (or yay / pikaur / whatever you use). the PKGBUILD lives in aur/klear and pulls nanovg in as a separate git source.
run
./bin/klear
if you're in a hyprland session it should just work. if HYPRLAND_INSTANCE_SIGNATURE happens to be unset, klear scans /run/user/$UID/hypr/ and picks the active one, and XDG_RUNTIME_DIR falls back to /run/user/$UID. sway works the same way through SWAYSOCK, niri through NIRI_SOCKET, and mangowc through XDG_CURRENT_DESKTOP=mango (the workspace pill talks dwl-ipc-v2 through the mmsg CLI, so you'll need the mmsg binary that ships with mangowc).
ipc / cli
the binary is also its own client, which i thought was a neat trick — any first arg other than start gets sent over /tmp/klear-bar.sock instead of starting a new bar.
klear launch # toggle the app launcher
klear launch on # force open
klear launch off # force close
klear sidebar # toggle the control-center panel
klear settings # toggle the settings window
klear magnifier # toggle a loupe-magnifier above the cursor (also: -m)
klear -L # lock the session
klear unlock # emergency unlock if the lock ui wedges
klear status # current bar state
klear config # path to theme.json
klear kill # stop klear cleanly
klear help # list commands
anything it doesn't recognize just prints the help text. you can bind these in hyprland with something like bind = SUPER, SPACE, exec, klear launch.
config
the theme lives at $XDG_CONFIG_HOME/klear/theme.json (or ~/.config/klear/theme.json). the first launch writes out every field with sensible defaults — edit it and klear hot-reloads, so most changes show up without a restart. (barPosition is the one exception, since it sets the wlr layer-shell anchor at startup.)
- colors are
"0xAARRGGBB"strings - numbers are plain numbers, bools are bools, strings are strings
//line comments and/* ... */block comments are allowed- missing keys fall back to defaults, so you can keep a stripped-down config if you want
- you can also drop a
colors.jsonnext to it as an overlay — keys in there overridetheme.json. matugen writes this file (more on that below)
some of the knobs i reach for most:
barPosition—"top"|"bottom"|"left"|"right"barStyle—"pills"(separate widget chrome) or"unified"(one bar-wide background pill)clockFormat— strftime, e.g."%I:%M %p","%H:%M:%S","%a %H:%M"clockUseFont— set it tofalseto switch the clock to my old vector "bubble digit" thinguseScreenShader,useKlearGlass,useCaustic,useChromaticFringe,useRefraction— turn the fancy glass effects on or offglassRefractStrength,glassChromaStrength,glassEdgePx,glassRimGlow— refraction tuningshowShadow— drop shadow under every pillbatteryDefaultOn— the starting value of the runtime togglenotifDefaultTimeoutMs— how long a notification banner sticks around before it fadeslockBackdropBlur,lockBackdropPasses,lockBackdropSaturation,lockBackdropDim— lockscreen background blur tuninglockCardBg,lockCardCornerRadius,lockClockColor,lockDotColor,lockFieldBg— lockscreen card/field/text colorswallpaperDir— where the settings panel looks for wallpapers (auto-detects if you leave it empty)matugenAutoRun/matugenScheme/matugenMode— see below
matugen
if ~/.config/klear/colors.json exists, klear loads it on top of theme.json — which is handy for matugen generating a palette from your wallpaper. only the keys in colors.json get overridden, everything else keeps your theme.json values.
there are two ways to wire it up:
self-contained (no matugen config needed). flip matugenAutoRun to true in theme.json, or do it in the settings panel → wallpapers tab. then when you change wallpapers through klear, it runs matugen --json hex itself, parses the material palette, maps it onto klear's color keys, and writes colors.json directly. you don't need a klear template in your matugen config for this.
external matugen template. if you already use matugen and want klear to re-theme whenever your other targets do, drop contrib/matugen/klear.json into ~/.config/matugen/templates/ and add this to ~/.config/matugen/config.toml:
[templates.klear]
input_path = "~/.config/matugen/templates/klear.json"
output_path = "~/.config/klear/colors.json"
then any matugen image /path/to/wallpaper.jpg rewrites colors.json and klear hot-reloads.
picking a different scheme (scheme-tonal-spot, scheme-vibrant, ...) or mode (dark/light) in the settings panel re-runs matugen on the current wallpaper right away, so the change actually shows up.
klearglass / blur
klear's surface is semi-transparent on purpose — the pills, popups, and notifications are drawn with low-alpha fills so the desktop shows through live. for the klearglass look to work, the compositor needs to blur whatever's behind klear.
klear installs the layer rules itself at startup (hyprctl keyword layerrule blur,klear-bar + ignorezero + ignorealpha 0.1), so you don't have to add anything to hyprland.conf for blur.
you can dial the blur strength in the global decoration block of hyprland.conf — blur { size = 4, passes = 2 } is a decent "less blurred" baseline, and bumping passes softens it more:
decoration {
blur {
enabled = yes
size = 4
passes = 2
new_optimizations = yes
}
}
on top of the compositor blur, hyprland's screen_shader runs klear's per-pill refraction shader, so each glass surface picks up the rim distortion + sun-angle highlight (the highlight angle ticks every minute to track time of day).
layout
src/
main.kt wayland setup, render loop, workers, action plumbing
Shell.kt popen + sysfs helpers, hasCmd cache
Compositor.kt picks Hyprland, Sway, Niri, or MangoWC based on env
Hyprland.kt socket1 + socket2 readers
Sway.kt i3-ipc binary protocol reader
Niri.kt niri ipc EventStream reader
Mango.kt mangowc — shells out to mmsg (dwl-ipc-v2 client) for tag, selmon, kb_layout
Dbus.kt StatusNotifierWatcher, dbusmenu, org.freedesktop.Notifications — hand-rolled wire protocol
Battery.kt /sys/class/power_supply reader
Calendar.kt ics feed parser + alarms for the clock-click popup
PowerProfiles.kt wraps powerprofilesctl
Wallpaper.kt finds the active wallpaper from hyprpaper / swww / swaybg
AppLauncher.kt scans .desktop files (+ flatpak) for the launcher
SystemControls.kt wi-fi (nmcli/iwctl), bluetooth, audio (wpctl), brightness + capslock device probes
SystemUsage.kt cpu/mem for the bottom popup
Visualizer.kt cava-driven audio spectrum
Weather.kt open-meteo + geoip
Sun.kt sunrise/sunset for the bottom popup gradient
IdleInhibit.kt systemd-inhibit wrapper for the caffeine toggle
Settings.kt wallpaper backends + matugen runner
ThemeApply.kt applies matugen output to theme.json
Lockscreen.kt ext-session-lock-v1 client + pam auth + blurred screen-capture backdrop
ShaderInstaller.kt builds + installs the hyprland screen_shader, manages layer rules
Config.kt json(c) theme loader/dumper
IPC.kt /tmp/klear-bar.sock daemon ↔ cli
time.kt strftime wrapper
ui/
renderer.kt every pill, popup, refraction effect, the OSD popups
theme.kt every tweakable value, single data class
state.kt atomics shared between threads
Wakeup.kt eventfd helpers to wake the render loop and worker threads
Anim.kt, Color.kt, Glyphs.kt small shared helpers
lib/
nanovg/, nanovg_bridge.c C side
protocols/ wayland protocol XML → C
*.klib *.o build artifacts
src/def/
wayland.def, nanovg.def, pam.def cinterop config
contrib/
matugen/klear.json ready-to-use matugen template
lockscreen
klear -L enters a session lock. it grabs the desktop with wlr-screencopy first, blurs that on the cpu, and renders it as the lock background. on top of that sits a card with the clock, your username, and a password field, and auth goes through pam. while it's locked, klear swaps in a lock-only screen_shader (at ~/.cache/klear/lock.frag) so the area around the card refracts the blurred backdrop full-screen — you can tune the rim/halo strength through the lock card theme settings.
install the pam service so klear can check against your normal password — otherwise it falls back to /etc/pam.d/login, which rejects in-session unlocks:
sudo install -Dm644 etc/pam.d/klear /etc/pam.d/klear
if the lock ever wedges (compositor stuck in lock-prevent state, or klear died), open a tty and run bash unlock-recovery.sh — it walks you through klear unlock, a sigterm, and the hyprctl misc:allow_session_lock_restore recipe. i added this after locking myself out once, so trust me, it helps.
bind it in hyprland with something like bind = SUPER, L, exec, klear -L.
license
mit. see LICENSE. do whatever you want with it, just don't blame me if your bar catches fire.






