PUBLIC REPOSITORY
PopOS taskbar that gives you the windows feel.
By Batty 251 (@batty-251) · Licence: MIT · 1 published version · 45 files · 1.1 MB of source · Last published 2026-09-29
A public project on CodeRook. Browse it at https://coderook.com/batty-251/winbar.
A from-scratch Windows 11 style taskbar for the COSMIC desktop (Wayland), built
with libcosmic (iced + layer-shell + xdg-popup)
and cosmic-client-toolkit.
Version 1.1.0: tested on Pop!_OS 24.04 with COSMIC 1.9, on two monitors at 90%
fractional scale.
The bar: Start (Pop!_OS logo), Search, Task view and pinned apps on the left, open apps
in the centre, and the tray, Quick Settings, clock and power on the right.


| Start menu | Calendar | Quick Settings | Power |
|---|---|---|---|
|  |  |  |  |
The whole bar on one 1920×1080 monitor: [docs/screenshots/bar.png](docs/screenshots/bar.png).
ext_background_effect_v1 when available), follows COSMIC's dark/light mode. All sizes are logical pixels (crisp at fractional scales such as 90 %).app_icon_size, bar_height).^ hidden-icons grid, up to 4 tray icons (first-registered first; StatusNotifierItem host with dbusmenu rendering), Quick Settings button (network, volume, battery if a system battery exists), stacked clock with calendar flyout (both follow COSMIC's Settings → Time & Language live: 12/24 h, seconds, weekday, date on/off, custom strftime format, first day of the week; names/formats localised from COSMIC's region (LC_TIME via systemd-localed) with ICU, like COSMIC's own time applet; timezone changes, NTP clock steps and resume are picked up immediately), a power button (lock / sign out / sleep / restart / shut down), show-desktop sliver.start_pinned, seeded from the taskbar pins) above an A–Z grid of every installed app (NoDisplay/Hidden/OnlyShowIn respected; duplicate copies such as Flatpak + distro package collapsed). Right-click an app: Open, Pin/Unpin Start, Pin/Unpin taskbar. Footer: File Explorer, Terminal, Settings, your name and a power menu. Apps are launched like taskbar apps (xdg-activation token, own systemd scope) and the menu closes. use_cosmic_app_library = true makes Start open COSMIC's App Library instead. (The Super key stays with COSMIC; there is no supported way for a client to take it.)pactl, live updates via pactl subscribe), brightness (only if /sys/class/backlight has a device), settings shortcut, power menu (lock, sign out, sleep, restart, shut down — sign out/restart/shut down use COSMIC's own cosmic-osd confirmation dialogs, like the COSMIC power applet).xdg_popups parented on the bar's layer surface; only one is open at a time; clicking outside dismisses them (popup grab), Esc closes them.Needs Rust stable and the usual libcosmic build deps
(libxkbcommon-dev libwayland-dev libfontconfig-dev libfreetype-dev libexpat1-dev
libinput-dev libudev-dev libdbus-1-dev clang just). Build output goes to~/.cache/winbar-target when you build with just (override it with CARGO_TARGET_DIR).
Plain cargo build uses ./target as usual.
just test # unit + private-bus integration tests
just install # release build -> ~/.local/bin/winbar, ~/.local/bin/winbar-switch,
# ~/.config/autostart/winbar.desktop (created with Hidden=true)
just dev # run from the tree at the TOP edge, alongside COSMIC's panelwinbar-switch on # back up COSMIC panel entries, set them to [], enable autostart, start winbar
winbar-switch off # stop winbar, then restore COSMIC's entries exactly as backed up, disable autostart
winbar-switch statusCOSMIC's panel reads ~/.config/cosmic/com.system76.CosmicPanel/v1/entries (normally["Panel"]) and reloads it live, so no logout is needed. The backup lives in~/.local/state/winbar/cosmic-panel-entries.backup. Both commands are idempotent.
~/.config/winbar/config.toml is created on first start; pinned is initialised from
COSMIC's dock favourites (~/.config/cosmic/com.system76.CosmicAppList/v1/favorites).
Pin/unpin from the app context menu rewrites the file. Keys:
| key | default | meaning |
|---|---|---|
pinned | COSMIC favourites | desktop ids (or app ids) in order |
edge | "bottom" | bottom or top (--edge overrides) |
bar_height | 54 | bar height |
app_icon_size | 32 | icon size for apps and Start/Search/Task view (button = icon + 16) |
show_desktop_button | true | far-right sliver |
outputs | [] | monitors (connector names, e.g. ["DP-1"]; see winbar ctl dump → bars) that get a bar; [] = every monitor. Other monitors get no bar and no reserved space. If none of the listed monitors is connected, the first connected one gets the bar |
windows_on_all_monitors | true | false = each bar shows only windows on its monitor |
current_workspace_only | false | hide windows of inactive workspaces |
blur, opacity | true, 0.85 | translucency; opaque when the compositor can't blur |
tray_visible_max | 4 | tray icons shown on the bar; the rest go into the ^ box |
tray_hidden | [] | SNI Ids always placed in the overflow |
show_power_button | true | power button after the clock |
start_pinned | taskbar pins | desktop ids pinned in the Start menu |
start_width, start_height | 640, 720 | Start menu size (clamped to the monitor) |
start_icon | "start-here-pop-os" | icon name (searched in the Pop theme, your theme, then hicolor), absolute SVG/PNG path, or "builtin" for the four-squares glyph |
use_cosmic_app_library | false | Start opens COSMIC's App Library instead |
The config is read at start-up (restart winbar after hand edits). Clock/date settings
are not in this file: winbar reads COSMIC's own~/.config/cosmic/com.system76.CosmicAppletTime/v1/* (military_time,show_seconds, show_weekday, show_date_in_top_panel, first_day_of_week
— 0 = Monday … 5 = Saturday, otherwise Sunday — and format_strftime, which overrides
the rest; %n starts a second line) and follows changes live.
Logs: ~/.local/state/winbar/winbar.log (rotated at 1 MiB to winbar.log.1).RUST_LOG=debug winbar --stderr for verbose output.
winbar registers as a host with the existing org.kde.StatusNotifierWatcher (on COSMIC
that is the socket-activated cosmic-applet-status-area --status-notifier-watcher
daemon, which exits ~1–2 min after the last COSMIC panel applet goes away). If the name
is free, winbar becomes the watcher itself (RegisterStatusNotifierItem/Host,Registered* properties and signals, items dropped when their bus name vanishes) and
re-seeds it with the items it already knew. Items that own a conventionalorg.kde/org.freedesktop.StatusNotifierItem-* name but aren't registered with the
current watcher (some apps, e.g. FxSound, register only once at start-up and are lost
whenever a watcher restarts) are adopted too. The owner is tracked continuously, so the
switch happens automatically in both directions.
Left click → Activate (or the menu for ItemIsMenu items), right click → the item'scom.canonical.dbusmenu rendered in a winbar popup (Event(id,"clicked")), middle →SecondaryActivate, wheel → Scroll. An xdg-activation token is passed withProvideXdgActivationToken before activations.
winbar-test-sni [--id NAME] [--pixmap] exports a test item with a small menu and logs
every call — handy for checking the tray on the real or a private bus.
D-Bus interface dev.winbar.Debug at /dev/winbar/Debug on the session bus (bus namedev.winbar.Winbar, which also enforces a single instance). CLI wrapper:
winbar ctl dump # JSON: bars, groups, windows, tray, popup, quick settings
winbar ctl open calendar|quick|power|overflow [OUTPUT]
winbar ctl open tray-menu:steam | app-menu:<group-key> | thumbs:<group-key> [OUTPUT]
winbar ctl close
winbar ctl click start|search|taskview|desktop|app:<key>|tray:<id> [left|right|middle]
winbar ctl menu <id> # tray menu item id, or pin|close-all|launch|action:<id>
# for app menus, activate:<win>|close:<win> for thumbnails
winbar ctl quick wifi|bluetooth|mute|volume:N|brightness:N
winbar ctl hover <key> [leave] # simulate hovering an app button
winbar ctl open start [OUTPUT] # Start menu
winbar ctl start-query <text> # type into the Start search
winbar ctl key up|down|left|right|enter|escape
winbar ctl start-icon <name|path|builtin> # preview a Start icon (not saved)
winbar ctl tray-last [item-bus-name] # last tray activation (dev.winbar.Tray1), JSONGroup keys are lower-case desktop ids (see dump). Popups opened through the IPC are
created without a grab (there is no input serial), so close them with winbar ctl close.
winbar tells tray apps where their icon is, so they can open their own popup above it.
On the session bus winbar owns dev.winbar.Tray, object /dev/winbar/Tray,
interface dev.winbar.Tray1:
GetLastActivation(s item_key) -> (b found, s output,
i icon_x, i icon_y, i icon_w, i icon_h,
i out_x, i out_y, i out_w, i out_h,
i bar_height, s edge, t timestamp_ms)
signal Activated(s item_key, s output, i icon_x, i icon_y, i icon_w, i icon_h)item_key: your item's bus name — the unique name (:1.895) or the well-known name you registered (org.kde.StatusNotifierItem-1234-1); the full registered service string also works. "" returns the most recent activation of any item.icon_* is the tray icon's rectangle, or the ^ button's rectangle when the item was clicked inside the hidden-icons box. out_* is the monitor, bar_height is in logical px and edge is "bottom" or "top". timestamp_ms is Unix time in ms.Activated for every left, middle and right click before it calls Activate / SecondaryActivate / ContextMenu on your item, so querying from inside your Activate handler returns the new values.Activate(x, y), SecondaryActivate(x, y) and ContextMenu(x, y) receive the icon's top-centre point (icon_x + icon_w/2, icon_y).Example: busctl --user call dev.winbar.Tray /dev/winbar/Tray dev.winbar.Tray1 GetLastActivation s ""
or winbar ctl tray-last.
winbar also exposes the toplevel windows it tracks. On the session bus it ownsdev.winbar.Windows, object /dev/winbar/Windows, interfacedev.winbar.Windows1:
List() -> a(ssssbbbiiiib) # one struct per toplevel:
# s id, s app_id, s title, s output,
# b focused, b minimized, b fullscreen,
# i x, i y, i w, i h, b on_active_workspace
GetFocused() -> s id # "" if no toplevel is activated
Activate(s id) -> b
Minimize(s id) -> b
Close(s id) -> b
CaptureWindow(s id) -> s png_path
signal FocusChanged(s id) # "" when nothing is focused
signal WindowsChanged()"12"), the same id values winbar ctl dump prints under windows. An id is valid for the lifetime of the window and never reused while winbar runs; ids restart from 1 when winbar restarts.List returns windows on all workspaces (and minimized ones), in **creation order** (ascending id; windows that existed when winbar started come first in the order the compositor announced them). on_active_workspace tells whether the window is on a currently visible workspace (use it to skip windows that aren't on screen).focused is the compositor's activated state. output is the output holding the largest part of the window ("" if none).x, y, w, h is in global compositor logical coordinates (the same space as dev.winbar.Tray1), converted from COSMIC's output-relative zcosmic_toplevel_handle_v1.geometry using the output's logical position. It is -1, -1, 0, 0 when unknown. It is the window's frame as COSMIC places it, including COSMIC's server-side title bar when the window has one.Activate / Minimize / Close return false for an unknown id or when the compositor doesn't offer that zcosmic_toplevel_manager_v1 capability; true means the request was sent (the compositor may still ignore it).CaptureWindow captures the window at full resolution (ext-image-copy-capture into shared memory), writes $XDG_RUNTIME_DIR/winbar-capture-<id>-<n>.png (mode 0600) and returns the path. The caller owns the file and must delete it. The image is the client surface only (no server-side title bar), in the buffer size the compositor offers, so it can differ from w × h. Minimized windows can be captured. It fails with a D-Bus error (InvalidArgs for an unknown id, Failed otherwise, e.g. after a 2 s timeout).WindowsChanged covers any add/remove/title/state/geometry/workspace change; when the activated window changed, FocusChanged is sent first. Re-read List / GetFocused after a signal.Examples:
busctl --user call dev.winbar.Windows /dev/winbar/Windows dev.winbar.Windows1 List
busctl --user call dev.winbar.Windows /dev/winbar/Windows dev.winbar.Windows1 GetFocused
busctl --user call dev.winbar.Windows /dev/winbar/Windows dev.winbar.Windows1 Activate s 12
busctl --user call dev.winbar.Windows /dev/winbar/Windows dev.winbar.Windows1 CaptureWindow s 12
dbus-monitor --session "type='signal',interface='dev.winbar.Windows1'" # watch the signalswinbar renders in software only (libcosmic's tiny-skia renderer through softbuffer), so
every surface uses wl_shm buffers, never dmabuf. Window thumbnails are captured intowl_shm buffers as well (about 1 fps, only while a thumbnail popup is open). You can
check this with WAYLAND_DEBUG=1 winbar --edge top 2>&1 | grep -E 'linux_buffer_params|create_buffer'.
The only dmabuf-related call is iced binding the zwp_linux_dmabuf_v1 global, which it
never uses to create buffers.
src/lib.rs library (unit tested)
clock.rs en-AU time/date formatting, month grid, minute alignment
config.rs config.toml load/save, COSMIC favourites import
desktop.rs .desktop loading, app_id -> desktop entry matching, Exec parsing
grouping.rs window grouping/ordering and click behaviour
layout.rs deterministic bar geometry (popup anchoring)
sni.rs SNI service parsing, ARGB pixmaps, item props, dbusmenu layout parsing
watcher.rs org.kde.StatusNotifierWatcher (bookkeeping + zbus interface)
tray.rs SNI host / watcher fail-over service (tokio)
tray_api.rs dev.winbar.Tray1 (tray icon activation geometry for apps)
windows_api.rs dev.winbar.Windows1 (window list, actions, capture, change signals)
start.rs Start menu: app filtering/dedupe, search ranking, categories, icon lookup
timefmt.rs clock/calendar text from COSMIC's time settings (ICU4X, jiff strtime)
ticker.rs realtime-aligned ticks (timerfd, CANCEL_ON_SET), timezone/locale/resume events
quick.rs NetworkManager, BlueZ, pactl, backlight, UPower (event driven)
wayland.rs separate Wayland connection thread: cosmic toplevel info/manager,
ext-workspace, ext-image-copy-capture thumbnails, xdg-activation
thumbs.rs thumbnail scaling
launch.rs process spawning (transient systemd scopes), argv building
ipc.rs debug D-Bus interface + `winbar ctl`
logging.rs capped rotating log file
test_item.rs test StatusNotifierItem + dbusmenu
src/ui/ libcosmic application (bar + popup views, styles)
tests/watcher_bus.rs private dbus-daemon integration test (watcher mode, fail-over)
tests/windows_bus.rs private dbus-daemon integration test (dev.winbar.Windows1)iced_wgpu is still compiled in, because libcosmic's wayland feature depends on it, but it is never used (the renderer type is iced_tiny_skia::Renderer).pinned instead).GPL-3.0-only (the design follows COSMIC's GPL-3.0 applets).
PUBLIC REPOSITORY