batty-251/winbar

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.

Versions

Files in the latest version

README

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.

Screenshots

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.

![winbar, left end](docs/screenshots/bar-left.png)
![winbar, right end](docs/screenshots/bar-right.png)

Start menuCalendarQuick SettingsPower
![Start menu](docs/screenshots/start-menu.png)![Calendar](docs/screenshots/calendar.png)![Quick Settings](docs/screenshots/quick-settings.png)![Power menu](docs/screenshots/power-menu.png)

The whole bar on one 1920×1080 monitor: [docs/screenshots/bar.png](docs/screenshots/bar.png).

Features

Build / install

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 panel

Switching COSMIC's panel off/on

winbar-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 status

COSMIC'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.

Configuration

~/.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:

keydefaultmeaning
pinnedCOSMIC favouritesdesktop ids (or app ids) in order
edge"bottom"bottom or top (--edge overrides)
bar_height54bar height
app_icon_size32icon size for apps and Start/Search/Task view (button = icon + 16)
show_desktop_buttontruefar-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_monitorstruefalse = each bar shows only windows on its monitor
current_workspace_onlyfalsehide windows of inactive workspaces
blur, opacitytrue, 0.85translucency; opaque when the compositor can't blur
tray_visible_max4tray icons shown on the bar; the rest go into the ^ box
tray_hidden[]SNI Ids always placed in the overflow
show_power_buttontruepower button after the clock
start_pinnedtaskbar pinsdesktop ids pinned in the Start menu
start_width, start_height640, 720Start 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_libraryfalseStart 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.

Tray (StatusNotifierItem)

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 conventional
org.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's
com.canonical.dbusmenu rendered in a winbar popup (Event(id,"clicked")), middle →
SecondaryActivate, wheel → Scroll. An xdg-activation token is passed with
ProvideXdgActivationToken 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.

Debug IPC

D-Bus interface dev.winbar.Debug at /dev/winbar/Debug on the session bus (bus name
dev.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), JSON

Group 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.

For app developers: where was my tray icon clicked?

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)

Example: busctl --user call dev.winbar.Tray /dev/winbar/Tray dev.winbar.Tray1 GetLastActivation s ""
or winbar ctl tray-last.

For app developers: window list, focus, capture

winbar also exposes the toplevel windows it tracks. On the session bus it owns
dev.winbar.Windows, object /dev/winbar/Windows, interface
dev.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()

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 signals

Rendering

winbar renders in software only (libcosmic's tiny-skia renderer through softbuffer), so
every surface uses wl_shm buffers, never dmabuf. Window thumbnails are captured into
wl_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.

Architecture

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)

Known limitations

License

GPL-3.0-only (the design follows COSMIC's GPL-3.0 applets).

PUBLIC REPOSITORY

Opening batty-251/winbar…

Loading the public version history and verified file tree.