mirror of
https://github.com/outbackdingo/hyprcosmic.git
synced 2026-08-25 14:53:21 +00:00
Generate the waybar config, and fill the bar out
The bar had six modules and one dead click: pulseaudio's on-click ran pavucontrol, which is not installed. It now opens `cosmic-settings sound`, with middle-click as a mute toggle. Added: mpris, bluetooth, temperature, idle_inhibitor, privacy, a swaync notification button, and power-profiles- daemon -- and HyDE's pill styling, so each module is its own rounded chip rather than text in a row. No backlight module. This machine's panel has no sysfs backlight interface, so it would render as a permanent error. The rest of this is about the icons, which have now been got wrong enough times to deserve a mechanism. Private Use Area characters do not survive being typed. Writing the previous version I put a comment at the top of the file saying every glyph was an escape, then typed literal glyphs into the same file; the codepoint dump found `format-bluetooth` had picked up a stray U+F293 and another field had two glyphs where I had written one. Nothing errors -- waybar is perfectly happy to render a label that is one space. So the config is generated, not written. config.jsonc.in is pure ASCII with @@TOKEN@@ placeholders, generate-config.py holds the name-to-codepoint table and emits `\uXXXX` escapes, and it asserts its own output `.isascii()` before writing. A hand-typed glyph now cannot reach the file. The delimiter is doubled because single `@NAME@` collided with wpctl's `@DEFAULT_AUDIO_SINK@`, which the generator caught as an unknown token rather than mangling. Every codepoint was checked against the installed font with `fc-list ":charset=..."`. HyDE's own values do not all survive that: its muted glyph U+FA80 is an old Material Design Icons codepoint that Nerd Fonts v3 moved, so copying upstream verbatim would have shipped tofu. Three were replaced. The font stack named "FontAwesome 6 Free" first, which resolves to the Regular face and carries a fraction of the icon set -- the icons that did appear were coming from accidental per-character fontconfig fallback. JetBrainsMono Nerd Font goes first now. One CSS note, because the failure mode is not obvious: GTK has no `:empty` pseudo-class, and an unknown pseudo-class does not skip the rule, it rejects the whole stylesheet and waybar exits 1. `#tray:empty` took the bar down. install-assets.sh grows a third category. The template and the generator live under config/ but must not be installed -- a file full of placeholders sitting next to the real config is a coin toss for whoever opens one first -- and the audit refuses to run until every file is classified, which is exactly what it is for.
This commit is contained in:
@@ -24,3 +24,13 @@
|
||||
* or once blur lands.
|
||||
*/
|
||||
@define-color bar-bg alpha(@main-bg, 0.85);
|
||||
|
||||
/* The pill behind each module. Derived rather than themed: HyDE has no name
|
||||
* for it, because in HyDE the pill IS the bar -- its modules sit on a
|
||||
* transparent strip and the compositor blurs the desktop behind them. Without
|
||||
* blur that reads as text floating on the wallpaper, so here the bar keeps a
|
||||
* background and the pills are a light lift off it.
|
||||
*
|
||||
* Derived from the foreground, not the background, so it stays visible whether
|
||||
* the theme is dark or light. */
|
||||
@define-color module-bg alpha(@main-fg, 0.08);
|
||||
|
||||
+211
-15
@@ -16,15 +16,55 @@
|
||||
// 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=<cp>` 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": 6,
|
||||
"spacing": 4,
|
||||
|
||||
"modules-left": ["ext/workspaces", "wlr/taskbar"],
|
||||
"modules-center": ["clock"],
|
||||
"modules-right": ["pulseaudio", "network", "cpu", "memory", "battery", "tray"],
|
||||
"modules-center": ["mpris", "clock", "privacy"],
|
||||
"modules-right": [
|
||||
"idle_inhibitor",
|
||||
"custom/swaync",
|
||||
"bluetooth",
|
||||
"pulseaudio",
|
||||
"network",
|
||||
"temperature",
|
||||
"cpu",
|
||||
"memory",
|
||||
"power-profiles-daemon",
|
||||
"battery",
|
||||
"tray"
|
||||
],
|
||||
|
||||
"ext/workspaces": {
|
||||
"format": "{name}",
|
||||
@@ -39,34 +79,190 @@
|
||||
"on-click-middle": "close"
|
||||
},
|
||||
|
||||
// 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} <i>{dynamic}</i>",
|
||||
"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": "<tt><small>{calendar}</small></tt>"
|
||||
},
|
||||
|
||||
// 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 }
|
||||
]
|
||||
},
|
||||
|
||||
"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": "muted",
|
||||
"format-icons": { "default": ["", "", ""] },
|
||||
"on-click": "pavucontrol"
|
||||
"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": " {signalStrength}%",
|
||||
"format-ethernet": " wired",
|
||||
"format-disconnected": " offline",
|
||||
"tooltip-format": "{ifname}: {ipaddr}"
|
||||
"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"
|
||||
},
|
||||
|
||||
"cpu": { "format": " {usage}%", "interval": 5 },
|
||||
"memory": { "format": " {percentage}%", "interval": 5 },
|
||||
// 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"
|
||||
},
|
||||
|
||||
"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": " {capacity}%",
|
||||
"format-plugged": " {capacity}%",
|
||||
"format-icons": ["", "", "", "", ""]
|
||||
"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 }
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
// 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=<cp>` 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": ["ext/workspaces", "wlr/taskbar"],
|
||||
"modules-center": ["mpris", "clock", "privacy"],
|
||||
"modules-right": [
|
||||
"idle_inhibitor",
|
||||
"custom/swaync",
|
||||
"bluetooth",
|
||||
"pulseaudio",
|
||||
"network",
|
||||
"temperature",
|
||||
"cpu",
|
||||
"memory",
|
||||
"power-profiles-daemon",
|
||||
"battery",
|
||||
"tray"
|
||||
],
|
||||
|
||||
"ext/workspaces": {
|
||||
"format": "{name}",
|
||||
"on-click": "activate"
|
||||
},
|
||||
|
||||
"wlr/taskbar": {
|
||||
"format": "{icon}",
|
||||
"icon-size": 18,
|
||||
"tooltip-format": "{title}",
|
||||
"on-click": "activate",
|
||||
"on-click-middle": "close"
|
||||
},
|
||||
|
||||
// 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} <i>{dynamic}</i>",
|
||||
"dynamic-order": ["title", "artist"],
|
||||
"dynamic-separator": " ",
|
||||
"max-length": 40,
|
||||
"interval": 1,
|
||||
"player-icons": { "default": "@@NOTE@@" },
|
||||
"status-icons": {
|
||||
"playing": "@@PLAY@@",
|
||||
"paused": "@@PAUSE@@",
|
||||
"stopped": "@@STOP@@"
|
||||
},
|
||||
"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": "<tt><small>{calendar}</small></tt>"
|
||||
},
|
||||
|
||||
// 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 }
|
||||
]
|
||||
},
|
||||
|
||||
"idle_inhibitor": {
|
||||
"format": "{icon}",
|
||||
"format-icons": {
|
||||
"activated": "@@CAFFEINE_ON@@",
|
||||
"deactivated": "@@CAFFEINE_OFF@@"
|
||||
},
|
||||
"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": "@@BELL@@",
|
||||
"notification": "@@BELL_DOT@@",
|
||||
"dnd-none": "@@BELL_OFF@@",
|
||||
"dnd-notification": "@@BELL_OFF@@",
|
||||
"inhibited-none": "@@BELL@@",
|
||||
"inhibited-notification": "@@BELL_DOT@@",
|
||||
"dnd-inhibited-none": "@@BELL_OFF@@",
|
||||
"dnd-inhibited-notification": "@@BELL_OFF@@"
|
||||
},
|
||||
"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": "@@BT_ON@@",
|
||||
"format-disabled": "@@BT_DISABLED@@",
|
||||
"format-off": "@@BT_OFF@@",
|
||||
"format-connected": "@@BT_ON@@ {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": "@@VOL_MUTE@@",
|
||||
"format-bluetooth": "{icon} {volume}%",
|
||||
"format-bluetooth-muted": "@@VOL_MUTE@@",
|
||||
"format-icons": {
|
||||
"headphone": "@@VOL_HEADPHONE@@",
|
||||
"headset": "@@VOL_HEADSET@@",
|
||||
"hands-free": "@@VOL_HANDSFREE@@",
|
||||
"phone": "@@VOL_PHONE@@",
|
||||
"car": "@@VOL_CAR@@",
|
||||
"default": ["@@VOL_LO@@", "@@VOL_MID@@", "@@VOL_HI@@"]
|
||||
},
|
||||
"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": "@@WIFI@@ {signalStrength}%",
|
||||
"format-ethernet": "@@ETH@@ wired",
|
||||
"format-linked": "@@ETH@@ {ifname}",
|
||||
"format-disconnected": "@@NET_OFF@@ offline",
|
||||
"tooltip-format": "{ifname}: {ipaddr}",
|
||||
"tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ifname}: {ipaddr}",
|
||||
"on-click": "cosmic-settings network"
|
||||
},
|
||||
|
||||
// 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": "@@TEMP@@ {temperatureC} C",
|
||||
"critical-threshold": 90,
|
||||
"interval": 5,
|
||||
"tooltip-format": "CPU package: {temperatureC} C"
|
||||
},
|
||||
|
||||
"cpu": {
|
||||
"format": "@@CPU@@ {usage}%",
|
||||
"interval": 5
|
||||
},
|
||||
|
||||
"memory": {
|
||||
"format": "@@MEM@@ {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": "@@PROFILE@@",
|
||||
"performance": "@@PERF@@",
|
||||
"balanced": "@@BALANCED@@",
|
||||
"power-saver": "@@SAVER@@"
|
||||
},
|
||||
"tooltip-format": "power profile: {profile}",
|
||||
"tooltip": true
|
||||
},
|
||||
|
||||
"battery": {
|
||||
"states": { "warning": 30, "critical": 15 },
|
||||
"format": "{icon} {capacity}%",
|
||||
"format-charging": "@@PLUG@@ {capacity}%",
|
||||
"format-plugged": "@@PLUG@@ {capacity}%",
|
||||
"format-icons": [
|
||||
"@@BAT_00@@",
|
||||
"@@BAT_10@@",
|
||||
"@@BAT_20@@",
|
||||
"@@BAT_30@@",
|
||||
"@@BAT_40@@",
|
||||
"@@BAT_50@@",
|
||||
"@@BAT_60@@",
|
||||
"@@BAT_70@@",
|
||||
"@@BAT_80@@",
|
||||
"@@BAT_90@@",
|
||||
"@@BAT_100@@"
|
||||
],
|
||||
"tooltip-format": "{timeTo}\n{power:0.1f}W",
|
||||
"on-click": "cosmic-settings power"
|
||||
},
|
||||
|
||||
"tray": { "icon-size": 18, "spacing": 8 }
|
||||
}
|
||||
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate waybar config.jsonc with \\uXXXX escapes from verified codepoints.
|
||||
|
||||
Every codepoint here was confirmed present in JetBrainsMono Nerd Font with
|
||||
`fc-list :charset=<cp>`. The template is pure ASCII and uses @TOKEN@
|
||||
placeholders so that no PUA character is ever typed by hand.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
# name -> codepoint. Verified present in the installed Nerd Font.
|
||||
ICONS = {
|
||||
"VOL_LO": 0xF026,
|
||||
"VOL_MID": 0xF027,
|
||||
"VOL_HI": 0xF028,
|
||||
"VOL_HEADPHONE": 0xF025,
|
||||
"VOL_HEADSET": 0xF025,
|
||||
"VOL_HANDSFREE": 0xF025,
|
||||
"VOL_PHONE": 0xF095,
|
||||
"VOL_CAR": 0xF1B9,
|
||||
"VOL_MUTE": 0xF075F,
|
||||
"BT_ON": 0xF293,
|
||||
"BT_DISABLED": 0xF294,
|
||||
"BT_OFF": 0xF00B2,
|
||||
"CPU": 0xF035B,
|
||||
"MEM": 0xF0EE0,
|
||||
"TEMP": 0xF2CA,
|
||||
"WIFI": 0xF1EB,
|
||||
"ETH": 0xF0200,
|
||||
"NET_OFF": 0xF092F,
|
||||
"PLUG": 0xF1E6,
|
||||
"CAFFEINE_ON": 0xF0176,
|
||||
"CAFFEINE_OFF": 0xF06CA,
|
||||
"PLAY": 0xF04B,
|
||||
"PAUSE": 0xF04C,
|
||||
"STOP": 0xF04D,
|
||||
"NOTE": 0xF001,
|
||||
"BELL": 0xF0F3,
|
||||
"BELL_DOT": 0xF009A,
|
||||
"BELL_OFF": 0xF1F6,
|
||||
"PERF": 0xF135,
|
||||
"SAVER": 0xF06C,
|
||||
"BALANCED": 0xF24E,
|
||||
"PROFILE": 0xF0E7,
|
||||
"BAT_00": 0xF008E,
|
||||
"BAT_10": 0xF007A,
|
||||
"BAT_20": 0xF007B,
|
||||
"BAT_30": 0xF007C,
|
||||
"BAT_40": 0xF007D,
|
||||
"BAT_50": 0xF007E,
|
||||
"BAT_60": 0xF007F,
|
||||
"BAT_70": 0xF0080,
|
||||
"BAT_80": 0xF0081,
|
||||
"BAT_90": 0xF0082,
|
||||
"BAT_100": 0xF0079,
|
||||
}
|
||||
|
||||
|
||||
def escape(cp: int) -> str:
|
||||
"""JSON escape for one codepoint, surrogate pair when above the BMP."""
|
||||
return json.dumps(chr(cp), ensure_ascii=True)[1:-1]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
template_path, out_path = sys.argv[1], sys.argv[2]
|
||||
text = open(template_path, encoding="ascii").read()
|
||||
|
||||
unknown = {t for t in re.findall(r"@@([A-Z0-9_]+)@@", text) if t not in ICONS}
|
||||
if unknown:
|
||||
print("unknown tokens:", sorted(unknown), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
used = set()
|
||||
|
||||
def sub(m):
|
||||
used.add(m.group(1))
|
||||
return escape(ICONS[m.group(1)])
|
||||
|
||||
out = re.sub(r"@@([A-Z0-9_]+)@@", sub, text)
|
||||
|
||||
if not out.isascii():
|
||||
print("generated file is not pure ASCII", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
open(out_path, "w", encoding="ascii").write(out)
|
||||
|
||||
unused = sorted(set(ICONS) - used)
|
||||
print(f"wrote {out_path}: {len(out.splitlines())} lines, {len(used)} icons used")
|
||||
if unused:
|
||||
print("unused icon names:", unused)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
+88
-5
@@ -3,10 +3,28 @@
|
||||
* No colour literals: every colour here is a name defined by palette.css and
|
||||
* possibly re-pointed by bridge-hyde.css. That separation is what lets a theme
|
||||
* change the palette without touching a single rule.
|
||||
*
|
||||
* The look is HyDE's: a strip carrying rounded pills rather than a flat row of
|
||||
* text. HyDE gets its pills to float by making the bar itself transparent and
|
||||
* letting the compositor blur the desktop behind them; cosmic-comp has no
|
||||
* rule-driven blur, so the bar keeps a background here and the pills lift off
|
||||
* it instead. See bridge-hyde.css for the two colours that implements.
|
||||
*/
|
||||
|
||||
* {
|
||||
font-family: "FontAwesome 6 Free", "Noto Sans", sans-serif;
|
||||
/* JetBrainsMono Nerd Font first, and it has to be first.
|
||||
*
|
||||
* This previously named "FontAwesome 6 Free", which is not even the
|
||||
* installed family's real name ("Font Awesome 6 Free") -- fontconfig's
|
||||
* fuzzy matching resolved it anyway, to the Regular face, which carries
|
||||
* only a small subset of the icon set. Every glyph outside that subset
|
||||
* then came from whatever fallback fontconfig picked per character, so the
|
||||
* bar's icons were only accidentally consistent.
|
||||
*
|
||||
* The Nerd Font carries the whole icon set and the text, so naming it
|
||||
* first makes one font answer for both. Every codepoint in config.jsonc
|
||||
* was checked against this family specifically. */
|
||||
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
@@ -18,15 +36,22 @@ window#waybar {
|
||||
color: @bar-fg;
|
||||
}
|
||||
|
||||
/* Workspaces: pills, with the active one filled rather than underlined. */
|
||||
#workspaces {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
#workspaces button {
|
||||
padding: 0 8px;
|
||||
padding: 0 10px;
|
||||
margin: 4px 2px;
|
||||
border-radius: 10px;
|
||||
color: @muted;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#workspaces button.active {
|
||||
color: @bar-fg;
|
||||
box-shadow: inset 0 -2px @accent;
|
||||
color: @wb-act-fg;
|
||||
background: @accent;
|
||||
}
|
||||
|
||||
#workspaces button:hover {
|
||||
@@ -36,28 +61,86 @@ window#waybar {
|
||||
|
||||
#taskbar button {
|
||||
padding: 0 6px;
|
||||
margin: 4px 1px;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#taskbar button.active {
|
||||
box-shadow: inset 0 -2px @accent;
|
||||
background: @module-bg;
|
||||
}
|
||||
|
||||
#taskbar button:hover {
|
||||
background: @wb-hvr-bg;
|
||||
}
|
||||
|
||||
/* Every status module gets the same pill. Listed one per line because waybar
|
||||
* takes the CSS id from the module name with `/` turned into `-`, so these are
|
||||
* not guessable from the config and are worth being able to read down. */
|
||||
#mpris,
|
||||
#clock,
|
||||
#privacy,
|
||||
#idle_inhibitor,
|
||||
#custom-swaync,
|
||||
#bluetooth,
|
||||
#pulseaudio,
|
||||
#network,
|
||||
#temperature,
|
||||
#cpu,
|
||||
#memory,
|
||||
#power-profiles-daemon,
|
||||
#battery,
|
||||
#tray {
|
||||
padding: 0 10px;
|
||||
margin: 4px 2px;
|
||||
border-radius: 10px;
|
||||
background: @module-bg;
|
||||
}
|
||||
|
||||
#clock {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* mpris renders nothing when no player is running. Without this it would still
|
||||
* draw an empty pill, so the padding goes away with the content. */
|
||||
#mpris {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#mpris.playing,
|
||||
#mpris.paused {
|
||||
padding: 0 10px;
|
||||
background: @module-bg;
|
||||
}
|
||||
|
||||
/* Same reasoning: privacy is only present while something is capturing. */
|
||||
#privacy {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#privacy-item {
|
||||
padding: 0 8px;
|
||||
margin: 4px 2px;
|
||||
border-radius: 10px;
|
||||
background: @module-bg;
|
||||
color: @warning;
|
||||
}
|
||||
|
||||
/* States. These recolour the text inside the pill rather than the pill, so a
|
||||
* warning cannot make a module unreadable against its own background. */
|
||||
#battery.warning { color: @warning; }
|
||||
#battery.critical { color: @critical; }
|
||||
#temperature.critical { color: @critical; }
|
||||
#network.disconnected { color: @critical; }
|
||||
#pulseaudio.muted { color: @muted; }
|
||||
#idle_inhibitor.activated { color: @accent; }
|
||||
#custom-swaync.notification { color: @accent; }
|
||||
#custom-swaync.dnd-none,
|
||||
#custom-swaync.dnd-notification { color: @muted; }
|
||||
|
||||
/* No `#tray:empty` rule to hide the pill when nothing is in the tray: GTK's
|
||||
* CSS has no :empty pseudo-class and rejects the whole stylesheet for it,
|
||||
* which is fatal rather than cosmetic. waybar exits and the bar never appears.
|
||||
*/
|
||||
|
||||
+12
-3
@@ -83,11 +83,20 @@ SESSION=(
|
||||
"cosmic-session/data/hyprcosmic.desktop:share/wayland-sessions/hyprcosmic.desktop:644"
|
||||
)
|
||||
|
||||
# Files under config/ that produce a SHARED file rather than being one. They are
|
||||
# inputs to a generator and have no place on the system: installing the template
|
||||
# would put a file full of @@TOKEN@@ placeholders next to the real config, and
|
||||
# whichever one a future reader opened first would be a coin toss.
|
||||
SOURCES=(
|
||||
"config/waybar/config.jsonc.in" # -> config/waybar/config.jsonc
|
||||
"config/waybar/generate-config.py" # the generator, and the icon table
|
||||
)
|
||||
|
||||
# Files under config/ that are deliberately NOT installed here, each with the
|
||||
# thing that does install it. This list is not decoration: the audit below
|
||||
# refuses to run unless every file under config/ appears in exactly one of the
|
||||
# two lists, so adding a file forces a decision about where it belongs instead
|
||||
# of letting it be quietly left out of both.
|
||||
# three lists, so adding a file forces a decision about where it belongs instead
|
||||
# of letting it be quietly left out of all of them.
|
||||
PER_USER=(
|
||||
"config/autostart" # ~/.config/hyprcosmic/autostart, by hand
|
||||
"config/cosmic.conf" # ~/.config/hyprcosmic/cosmic.conf, by hand
|
||||
@@ -96,7 +105,7 @@ PER_USER=(
|
||||
)
|
||||
|
||||
audit_config_tree() {
|
||||
local f rel known=" ${PER_USER[*]} " unclassified=()
|
||||
local f rel known=" ${PER_USER[*]} ${SOURCES[*]} " unclassified=()
|
||||
# Built with a loop, not `${SHARED[*]%%:*}`: that form strips the suffix
|
||||
# from the first element only and silently keeps the rest whole.
|
||||
for f in "${SHARED[@]}"; do known+="${f%%:*} "; done
|
||||
|
||||
Reference in New Issue
Block a user