// Waybar configuration for a HyprCosmic session. // // Waybar's shipped default (/etc/xdg/waybar/config.jsonc) is built entirely // around sway/* modules and shows nothing at all under cosmic-comp, so this // exists rather than a handful of overrides. // // Module choices are constrained by what cosmic-comp actually advertises on the // Wayland registry, which was checked against a live session: // // ext/workspaces ext_workspace_manager_v1 -- advertised by stock cosmic-comp. // wlr/taskbar zwlr_foreign_toplevel_management_v1 -- NOT in stock // cosmic-comp; provided by the HyprCosmic fork's Patch A. // Under a stock compositor this module stays empty and the // rest of the bar still works. // tray Needs a StatusNotifierWatcher. cosmic-panel normally // supplies one via cosmic-applet-status-area, and the // hyprcosmic profile disables the panel, so waybar hosts the // watcher itself here. // // ICONS. Every glyph below is a \uXXXX escape, never a literal character, and // this file is generated from config.jsonc.in for exactly that reason: // // - These are Private Use Area codepoints. They survive being copied, but // anything that re-types rather than copies them silently flattens them to // spaces or drops them next to a neighbour, and the damage is invisible in // a diff and indistinguishable from a font problem at runtime. // - An escape is plain ASCII, so it cannot be damaged that way, and it names // the intended glyph instead of leaving a box you have to identify. // // Every codepoint was checked against the installed JetBrainsMono Nerd Font // with `fc-list :charset=` before use. That check is worth doing rather // than copying HyDE's values: HyDE's pulseaudio module uses U+FA80 for the // muted state, an old Material Design Icons codepoint that Nerd Fonts v3 // moved. It is absent from the installed font and would render as tofu, so // U+F075F is used instead. // // NOT INCLUDED, deliberately: // // backlight The only device is acpi_video0, a firmware stub reporting // brightness 49 of max 49 -- permanently 100% -- whose sysfs // node is not writable by this user, with brightnessctl absent. // The module would render a readout that never changes and a // scroll action that never works: visible, plausible, and dead. // That is the same failure class as the `pavucontrol` click this // file used to carry, and it is not worth re-introducing for a // percentage that is always the same number. { "layer": "top", "position": "top", "height": 34, "spacing": 4, "modules-left": ["custom/keybinds", "ext/workspaces", "wlr/taskbar", "hyprland/window"], "modules-center": ["mpris", "clock", "privacy"], "modules-right": [ "systemd-failed-units", "idle_inhibitor", "custom/swaync", "bluetooth", "pulseaudio", "network", "disk", "temperature", "temperature#gpu", "custom/fan", "cpu", "memory", "power-profiles-daemon", "battery", "tray", "custom/power" ], // Opens the keyboard reference. First on the bar because it is the thing // you look for when you do not yet know where anything is. // // No "exec": this is a button, not a readout. A custom module with only a // format and an on-click runs nothing on an interval, so it costs a static // glyph and nothing else. // // The same script is on Super + Shift + / -- the binding is named in the // tooltip because a help button you have to reach for with the mouse has // not finished helping. "custom/keybinds": { "format": "\udb80\udf0c", "tooltip": true, "tooltip-format": "keyboard shortcuts\nSuper + Shift + /", "on-click": "hyprcosmic-keybinds" }, "ext/workspaces": { "format": "{name}", "on-click": "activate" }, "wlr/taskbar": { "format": "{icon}", "icon-size": 18, "tooltip-format": "{title}", "on-click": "activate", "on-click-middle": "close" }, // The taskbar above shows which windows exist; this shows which one has the // keyboard. Nothing else on the bar answered that. // // This is the one module here that talks to the fork's Hyprland IPC rather // than to a Wayland protocol, and it needs no compositor change: waybar // reads `j/activewindow` on startup and then follows `activewindow>>` on // .socket2.sock, both of which hypr_ipc already serves. A retitle of the // focused window propagates too -- src/hypr_ipc/sync.rs snapshots the // focused window as (class, title) and emits on any difference, which is // why upstream's separate `windowtitle` event is not needed here. Upstream // Hyprland emits from call sites and so must announce that case // separately; this fork diffs state on a 150 ms tick and cannot miss it. // // `separate-outputs` is left off deliberately: there is one bar on one // output, and with it on, the module reports that monitor's active window // rather than the focused one, which on a single head is the same answer by // a longer route. "hyprland/window": { "format": "\uf2d0 {title}", "max-length": 60, "tooltip": true, "tooltip-format": "{title}\n\nclass: {class}", // Titles are written for a window's own title bar, not for a 60-column // slot on a bar, so the worst offenders are trimmed to the part that // identifies the window. ECMAScript regex; the value is a replacement // string where $1 is the first capture. // // The separator is an em dash, and it is written as an escape // for the same reason every glyph in this file is: the template is read // as ASCII and the generator refuses to emit anything that is not, so a // literal em dash pasted here fails the build rather than reaching the // bar. COSMIC's applications really do use an em dash and not a hyphen // -- `j/clients` reports "dingo@fedora:~ COSMIC Terminal" -- so // a rule written with a hyphen matches nothing. // // There is deliberately no catch-all for the empty title. waybar's // documentation does not say whether a rule is applied on a full match // or a search, so an empty pattern is either "matches nothing but the // empty title" or "matches every title", and the difference is the // whole bar. The no-window case is handled in rules.css instead, the // same way #mpris already handles having nothing to say. "rewrite": { "(.*) [-\u2014] Vivaldi": "$1", "(.*) [-\u2014] COSMIC Terminal": "$1", "(.*) [-\u2014] COSMIC Text Editor": "$1", "(.*) [-\u2014] COSMIC Files": "$1", "(.*) [-\u2014] Mozilla Firefox": "$1" } }, // Renders nothing at all when no player is running, which is what we want // from a centre module: it takes no space until there is something to say. // playerctl backs every action here and is installed. "mpris": { "format": "{player_icon} {dynamic}", "format-paused": "{status_icon} {dynamic}", "dynamic-order": ["title", "artist"], "dynamic-separator": " ", "max-length": 40, "interval": 1, "player-icons": { "default": "\uf001" }, "status-icons": { "playing": "\uf04b", "paused": "\uf04c", "stopped": "\uf04d" }, "on-click": "playerctl play-pause", "on-click-middle": "playerctl previous", "on-click-right": "playerctl next", "on-scroll-up": "playerctl position 5+", "on-scroll-down": "playerctl position 5-", "tooltip-format": "{title}\nby {artist}\n{position} / {length}\n\nplayer: {player}" }, "clock": { "format": "{:%a %d %b %H:%M}", "tooltip-format": "{calendar}" }, // Only appears while something is actually capturing. Nothing is ignored: // an exception list is how a privacy indicator stops being one. "privacy": { "icon-spacing": 6, "transition-duration": 200, "modules": [ { "type": "screenshare", "tooltip": true }, { "type": "audio-in", "tooltip": true } ] }, // First on the right, and silent unless something is wrong. A unit that // failed at boot otherwise stays failed until the day you happen to run // `systemctl --failed` -- there is no other surface for it in a COSMIC // session, which has no equivalent of GNOME's abrt notification. // // Both scopes are watched. The user scope is where a session's own units // fail, which is the half that matters here and the half a system-only // check misses. "systemd-failed-units": { "format": "\udb80\udc26 {nr_failed}", "format-ok": "", "system": true, "user": true, "hide-on-ok": true, "on-click": "cosmic-term -e sh -c 'systemctl --failed; systemctl --user --failed; exec $SHELL'" }, "idle_inhibitor": { "format": "{icon}", "format-icons": { "activated": "\udb80\udd76", "deactivated": "\udb81\udeca" }, "tooltip-format-activated": "Caffeine on -- idling and blanking suppressed", "tooltip-format-deactivated": "Caffeine off -- normal power settings apply" }, // swaync is this session's notification daemon: it owns // org.freedesktop.Notifications and is D-Bus activated through // swaync.service, so it needs no autostart entry and this module starts it // on first contact if nothing else has. // // It is here because the hyprcosmic profile disables cosmic-panel and // COSMIC's notification applet lives in that panel. Without this there is // no way to reach notification history at all. "custom/swaync": { "format": "{icon}", "format-icons": { "none": "\uf0f3", "notification": "\udb80\udc9a", "dnd-none": "\uf1f6", "dnd-notification": "\uf1f6", "inhibited-none": "\uf0f3", "inhibited-notification": "\udb80\udc9a", "dnd-inhibited-none": "\uf1f6", "dnd-inhibited-notification": "\uf1f6" }, "return-type": "json", "exec": "swaync-client -swb", "on-click": "swaync-client -t -sw", "on-click-right": "swaync-client -d -sw", "tooltip": true, "escape": true }, // bluez is running with an unblocked hci0. The click target is COSMIC's own // settings page rather than blueman or overskride, neither of which is // installed here. "bluetooth": { "format": "\uf293", "format-disabled": "\uf294", "format-off": "\udb80\udcb2", "format-connected": "\uf293 {num_connections}", "tooltip-format": "{controller_alias}\n{num_connections} connected", "tooltip-format-connected": "{controller_alias}\n\n{device_enumerate}", "tooltip-format-enumerate-connected": "{device_alias}", "on-click": "cosmic-settings bluetooth" }, // The click used to be `pavucontrol`, which is not installed here: a button // that looked live and did nothing. cosmic-settings ships with the desktop, // so it cannot go missing the same way. Scroll and middle-click need no // helper -- waybar changes volume itself and wpctl comes with pipewire. "pulseaudio": { "format": "{icon} {volume}%", "format-muted": "\udb81\udf5f", "format-bluetooth": "{icon} {volume}%", "format-bluetooth-muted": "\udb81\udf5f", "format-icons": { "headphone": "\uf025", "headset": "\uf025", "hands-free": "\uf025", "phone": "\uf095", "car": "\uf1b9", "default": ["\uf026", "\uf027", "\uf028"] }, "scroll-step": 5, "tooltip-format": "{desc}\n{icon} {volume}%", "on-click": "cosmic-settings sound", "on-click-middle": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle" }, "network": { "format-wifi": "\uf1eb {signalStrength}%", "format-ethernet": "\udb80\ude00 wired", "format-linked": "\udb80\ude00 {ifname}", "format-disconnected": "\udb82\udd2f offline", "tooltip-format": "{ifname}: {ipaddr}", "tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ifname}: {ipaddr}", "on-click": "cosmic-settings network" }, // Root only. /home is on the same filesystem here, and a second entry for // it would be the same number twice. // // No warning threshold is set: waybar's disk module has no `states`, so the // colour comes from rules.css, which cannot see the percentage. The number // is the warning. "disk": { "path": "/", "format": "\udb80\udeca {percentage_used}%", "interval": 60, "tooltip-format": "{used} used of {total} on {path}, {free} free" }, // Tctl from k10temp, not thermal_zone0. The two read within a degree of // each other here, but the hwmon path names the CPU sensor explicitly. // // It is given as the PCI device's hwmon directory rather than // /sys/class/hwmon/hwmon6 because hwmonN numbering is assigned in probe // order and is not stable across boots. The PCI address is. "temperature": { "hwmon-path-abs": "/sys/devices/pci0000:00/0000:00:18.3/hwmon", "input-filename": "temp1_input", "format": "\uf2ca {temperatureC} C", "critical-threshold": 90, "interval": 5, "tooltip-format": "CPU package: {temperatureC} C" }, // A second instance of the same module. `temperature#gpu` is waybar's alias // syntax: everything before the # selects the module, everything after is // just a name, and it becomes #temperature.gpu in CSS. // // The dGPU's own sensor, not the CPU's. On this machine the two diverge by // 30 degrees under load, so one reading cannot stand for both. // // temp1_label reads "edge" -- the die edge, which is the sensor amdgpu // exposes on this card. There is no junction or memory sensor here to // prefer over it. // // hwmon-path-abs is safe for this one: the PCI device has exactly one // hwmonN inside it, unlike the asus platform device that custom/fan below // has to work around. "temperature#gpu": { "hwmon-path-abs": "/sys/devices/pci0000:00/0000:00:08.1/0000:05:00.0/hwmon", "input-filename": "temp1_input", "format": "\udb82\udcae {temperatureC} C", "critical-threshold": 95, "interval": 5, "tooltip-format": "GPU edge: {temperatureC} C" }, // Speed of the fastest fan, with every fan in the tooltip. See the script // for why this is not a temperature module with a different divisor and not // an hwmon-path-abs: both were tried and neither can name this sensor. // // Emits nothing on a machine with no readable fan, and waybar draws an // empty custom module as nothing, so this costs no space on hardware that // has none. "custom/fan": { "exec": "hyprcosmic-fan", "return-type": "json", "format": "\udb80\ude10 {} rpm", "interval": 5 }, "cpu": { "format": "\udb80\udf5b {usage}%", "interval": 5 }, "memory": { "format": "\udb83\udee0 {percentage}%", "interval": 5, "tooltip-format": "{used:0.1f}G of {total:0.1f}G" }, // Backed by tuned-ppd, which owns net.hadess.PowerProfiles on this system. "power-profiles-daemon": { "format": "{icon}", "format-icons": { "default": "\uf0e7", "performance": "\uf135", "balanced": "\uf24e", "power-saver": "\uf06c" }, "tooltip-format": "power profile: {profile}", "tooltip": true }, "battery": { "states": { "warning": 30, "critical": 15 }, "format": "{icon} {capacity}%", "format-charging": "\uf1e6 {capacity}%", "format-plugged": "\uf1e6 {capacity}%", "format-icons": [ "\udb80\udc8e", "\udb80\udc7a", "\udb80\udc7b", "\udb80\udc7c", "\udb80\udc7d", "\udb80\udc7e", "\udb80\udc7f", "\udb80\udc80", "\udb80\udc81", "\udb80\udc82", "\udb80\udc79" ], "tooltip-format": "{timeTo}\n{power:0.1f}W", "on-click": "cosmic-settings power" }, "tray": { "icon-size": 18, "spacing": 8 }, // Last on the bar, because it is the one button here that can end the // session or the uptime and the far corner is the hardest place to hit by // accident. // // It is here for the same reason "custom/swaync" is: the hyprcosmic profile // disables cosmic-panel, and COSMIC's power applet lives in that panel. // Without this there is no logout, reboot or shutdown anywhere in the // session -- the only way out was `systemctl reboot` from a terminal. // // The click runs the same script as the $mainMod SHIFT E binding rather // than calling systemctl here, so the confirmation step cannot be bypassed // by using the mouse. "custom/power": { "format": "\uf011", "tooltip": true, "tooltip-format": "Lock, suspend, log out, reboot or shut down", "on-click": "hyprcosmic-powermenu" } }