From e996b6626c85f8cefb0f357d128091e350ee0ec0 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 15 Sep 2026 12:58:31 +0200 Subject: feat: a waybar bar for DP-3, themed by udt and drawn with icons Builds the bar from scratch as a consumer of unified-desktop-theme: the palette comes from there, the layout and the modules live here. No font glyphs anywhere. Every symbol is a real icon from the icon theme GTK already uses, which needs all three of waybar's mechanisms because no single one covers everything: the icon-theme config key for the taskbar and tray, `image` modules driving wb-icon/wb-lang for volume, microphone, presentation mode and the keyboard flag, and CSS background-image for the workspaces, the clock pair and the launcher. The workspace icons have to be CSS because format-icons takes text rather than paths, and faking the strip with eight custom modules would have cost click-to-switch and the active and urgent states. The language flag goes the other way: hyprland/language styles as #language with no per-language class, so the flag could not be selected, and it is an image module. Icons keep their own colours: 2466 of the theme's 5169 panel icons carry a hardcoded gradient and are not recolourable, which is the intended look here. The two icons the theme lacks are shipped instead, and the monochrome Slackware mark takes the udt accent at install time. vms_dots.sh and privacy_dots.sh lived only in ~/bin, in no repository at all. They are versioned here now, symlinked back, and their hardcoded state colours read from the palette. Installs alongside the existing bar rather than over it, so DP-1 keeps working while this one is tried on DP-3. Co-Authored-By: Claude Opus 5 --- .gitignore | 10 ++ LICENSE | 338 +++++++++++++++++++++++++++++++++++++ README.md | 135 +++++++++++++++ SPEC.md | 208 +++++++++++++++++++++++ bin/privacy_dots.sh | 199 ++++++++++++++++++++++ bin/vms_dots.sh | 89 ++++++++++ bin/wb-icon | 171 +++++++++++++++++++ bin/wb-idle | 45 +++++ bin/wb-lang | 46 +++++ config.jsonc | 49 ++++++ icons/flag-gb.svg | 7 + icons/flag-it.svg | 7 + icons/slackware.svg | 5 + install.sh | 130 ++++++++++++++ modules/clock.jsonc | 35 ++++ modules/custom/launcher.jsonc | 13 ++ modules/custom/privacy_dots.jsonc | 12 ++ modules/custom/vmdot.jsonc | 10 ++ modules/extras/taskbar.jsonc | 18 ++ modules/extras/tray.jsonc | 8 + modules/hyprland/workspaces.jsonc | 26 +++ modules/image/idle_inhibitor.jsonc | 15 ++ modules/image/language.jsonc | 18 ++ modules/image/volume.jsonc | 44 +++++ styles/fonts.css | 19 +++ styles/global.css | 31 ++++ styles/modules.css | 180 ++++++++++++++++++++ styles/pills.css | 102 +++++++++++ styles/style.css | 19 +++ 29 files changed, 1989 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SPEC.md create mode 100755 bin/privacy_dots.sh create mode 100755 bin/vms_dots.sh create mode 100755 bin/wb-icon create mode 100755 bin/wb-idle create mode 100755 bin/wb-lang create mode 100644 config.jsonc create mode 100644 icons/flag-gb.svg create mode 100644 icons/flag-it.svg create mode 100644 icons/slackware.svg create mode 100755 install.sh create mode 100644 modules/clock.jsonc create mode 100644 modules/custom/launcher.jsonc create mode 100644 modules/custom/privacy_dots.jsonc create mode 100644 modules/custom/vmdot.jsonc create mode 100644 modules/extras/taskbar.jsonc create mode 100644 modules/extras/tray.jsonc create mode 100644 modules/hyprland/workspaces.jsonc create mode 100644 modules/image/idle_inhibitor.jsonc create mode 100644 modules/image/language.jsonc create mode 100644 modules/image/volume.jsonc create mode 100644 styles/fonts.css create mode 100644 styles/global.css create mode 100644 styles/modules.css create mode 100644 styles/pills.css create mode 100644 styles/style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ee526b --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +HANDOFF.md + +# Generated by udt-palette from the selected scheme. Tracking it would turn +# every scheme switch into a diff. +/styles/theme.css + +# Generated by install.sh: the accent is substituted into it. +/icons/slackware-accent.svg +idea1.png +idea2.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9efa6fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,338 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a37bd5 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +# waybar-theme-udt + +A waybar bar for DP-3: pill clusters, icons from the desktop's own icon theme +instead of font glyphs, and colours driven by +[unified-desktop-theme](../unified-desktop-theme). + + [◆] [📅 Wed 15/09] [🕑 14:32] [1..8] [●●] [ open apps ] [🔊] [tray] [⏻] [🇮🇹] + +Each bracket is a pill. Pills group into clusters with gaps between them, so +the bar reads as a few objects rather than one strip. Hover cross-fades +background and text over 0.3s; taskbar buttons underline in the accent. + +## Install + + ./install.sh + +Installs to `~/.config/waybar-udt/` and prints the command to run it. **The +existing bar at `~/.config/waybar/` is left alone**, so DP-1 keeps working +while this one is tried on DP-3. + +Needs `unified-desktop-theme` checked out next to this repo: the palette comes +from there, and `install.sh` fails clearly if it is missing. + +## No glyphs + +Every symbol on this bar is a real icon from `Material-Black-Plum-Suru`, the +theme GTK already uses. Icons keep their own colours, matching every other +application on the desktop. + +waybar offers three ways to draw one, and this bar uses all three because no +single one covers everything: + +| Mechanism | Used by | Why | +| --- | --- | --- | +| `icon-theme` config key | taskbar, tray | Native support, nothing else needed. | +| `image` module | volume, microphone, presentation mode, language | Runs a script that resolves a state to an icon path. | +| CSS `background-image` | workspaces, clock, launcher | `format-icons` takes text, not paths. | + +The consequence worth knowing: **the workspace icons live in +`styles/modules.css`, not in the module config.** Changing one means editing +CSS. The alternative was eight `custom` modules faking a workspace strip, +which would have cost click-to-switch and the active and urgent states. + +The language flag went the other way. `hyprland/language` styles as +`#language` and nothing else, with no per-language class, so the flag could +not be picked by a selector; it is an `image` module driven by `wb-lang` +instead. + +`wb-icon --selftest` checks every icon name the bar asks for, CSS ones +included, and `install.sh` runs it. A missing icon renders as an empty pill +with nothing to say why, so it fails while someone is watching instead. + +### Why the icons do not follow the accent + +2466 of the 5169 panel icons in `Material-Black-Plum-Suru` carry a hardcoded +gradient and are not recolourable. That is intended here: icons match the rest +of the desktop rather than the bar. + +The two icons this repo ships are the exception, because the icon theme has +neither. `icons/slackware.svg` (Simple Icons, CC0) is single-path monochrome +and takes the udt accent at install time. The two flags keep their national +colours. + +## Layout + +| Cluster | Modules | +| --- | --- | +| left | launcher, date, time, workspaces, VM dots | +| center | open applications | +| right | privacy dots, volume drawer, tray, presentation mode, language | + +No battery, temperature, CPU, memory or backlight: this is a desktop. + +The clock is the only text on the bar. An icon cannot say 14:32, so the icon +sits before the text, which is what makes the pair match the icon-only pills +around it. + +### Workspaces + +| WS | Icon | | +| --- | --- | --- | +| 1 | `web-browser` | 5 | `document-edit` | +| 2 | `utilities-terminal` | 6 | `applications-graphics` | +| 3 | `text-editor` | 7 | `internet-chat` | +| 4 | `network-server` | 8 | `input-gaming` | + +Workspace 4 uses the same icon as the quickshell drawer's VM tile. Names are +generic, so the bar does not lie when the application on a workspace changes. + +## Colours + +This repo holds the bar; udt holds the palette. `styles/theme.css` is +generated by `udt-palette` and gitignored, like every other generated file in +that project. + +The dot scripts are bash and cannot read `@define-color`, so `install.sh` +writes `~/.config/waybar-udt/dots.colors` for them to source. Both fall back to +Macchiato values when it is missing, so they still run standalone. + +## Layout of this repo + + config.jsonc entry point; `include`s every module file + modules/ one file per module, by source + styles/ style.css imports the rest in order + icons/ the two icons the icon theme lacks + bin/ wb-icon, wb-idle, and the two dot scripts + +Wildcard `include` has been broken since waybar v0.14.0, so `config.jsonc` +lists every module file explicitly. + +`bin/vms_dots.sh` and `bin/privacy_dots.sh` used to live only in `~/bin`, in no +repository. They are versioned here now and symlinked back; `install.sh` keeps +any real file it finds there as `.pre-udt` first. + +## Reload + + killall -SIGUSR2 waybar + +`reload_style_on_change` is on, so CSS edits apply by themselves. + +Visual changes are measured, not reasoned about: + + grim -o DP-3 /tmp/shot.png + +## License + +GPLv2 only. See `LICENSE`. + +## Development Approach + +This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback. + +All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions. + +The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..a4d2341 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,208 @@ +# waybar-theme-udt + +A waybar bar for DP-3, themed by [unified-desktop-theme][udt] and drawn with +icons from the desktop's own icon theme rather than font glyphs. + +[udt]: ../unified-desktop-theme + +## Goal + +Three things at once: + +- **No font glyphs.** Every module that shows a symbol shows a real icon from + `Material-Black-Plum-Suru`, the theme GTK already uses. Icons keep their own + colours, because that is what every other application on this desktop shows. +- **Colours from udt.** Pill backgrounds, text and states resolve from + `palette/roles-.conf`, so switching scheme recolours this bar with + everything else. +- **Pill clusters with transitions.** The grouped-capsule layout of `idea1.png` + with the hover and state transitions of `idea2.png`. + +The current bar on DP-1 is untouched. This one installs alongside it and is +pointed at DP-3. + +## Layout + + ┌ left ──────────────────────────────┐ ┌ center ┐ ┌ right ─────────────────────┐ + [◆] [📅 Wed 15/09] [🕑 14:32] [1..8] [●●] [ open apps ] [🔊 62%] [tray] [⏻] [🇮🇹] + launcher date time workspaces vm taskbar volume tray present lang + +Each bracket is one pill. Neighbouring pills in a cluster share a background +and weld together with asymmetric corner radii; clusters are separated by +transparent gaps, so the bar reads as groups rather than a continuous strip. + +### Modules + +| Position | Module | Icon source | Notes | +| --- | --- | --- | --- | +| left | `custom/launcher` | `slackware.svg`, shipped here | Accent background, the one always-coloured pill. Opens the quickshell drawer. | +| left | `clock#date` | `x-office-calendar` | Icon then text. Calendar tooltip kept. | +| left | `clock#time` | `clock` | Icon then text. | +| left | `hyprland/workspaces` | eight themed icons, via CSS | See below. | +| left | `custom/vmdot` | coloured dots | Dots keep their shape; colours move to roles. | +| center | `wlr/taskbar` | `icon-theme` config key | Per-window application icons. | +| right | `group/wireplumber` | `image` module | Drawer group kept as it is today. | +| right | `tray` | application-provided | | +| right | `idle_inhibitor` | `image` module | Presentation mode. | +| right | `image#lang` | `flag-it.svg` / `flag-gb.svg`, shipped here | An `image` module: `hyprland/language` styles as `#language` only, with no per-language class, so a flag cannot be selected in CSS. | + +Dropped from the current bar: `custom/breaktimer`, `custom/mail`, +`custom/privacy_dots` stays only if you want it on this bar too (it is +currently listed; say if it should follow the others out). No battery, no +temperature, no cpu, no memory, no backlight: this is a desktop. + +Clock is the one module that stays text. An icon cannot say 14:32; the icon +sits before the text, which is what makes it match the rest. + +### Workspace icons + +| WS | Icon | Was | +| --- | --- | --- | +| 1 | `web-browser` | browser glyph | +| 2 | `utilities-terminal` | terminal glyph | +| 3 | `text-editor` | code glyph | +| 4 | `network-server` | matches the quickshell drawer's VM tile (`U+F233`, Font Awesome `server`) | +| 5 | `document-edit` | pen on a document: the alternate text editor lives here | +| 6 | `applications-graphics` | graphics glyph | +| 7 | `internet-chat` | chat glyph | +| 8 | `input-gaming` | joypad: games and media players | + +All eight resolve in `Material-Black-Plum-Suru` today. + +## How icons reach the bar + +Three mechanisms, because waybar offers three and no single one covers +everything. This is the part worth reading before changing anything. + +**1. Native `icon-theme` (taskbar, tray).** `wlr/taskbar` takes `icon-theme` +and `icon-size` outright. Today it asks for `Papirus`, **which is not +installed**, so GTK has been silently falling back. Pointing it at +`Material-Black-Plum-Suru` is a bug fix, not a preference. + +**2. The `image` module (volume, presentation mode).** `image` runs `exec` and +expects `$path\n$tooltip` on stdout, so a small script maps state to an icon +name, resolves it through the GTK icon theme, and prints the path. One `image` +module is one icon, so a five-state volume indicator is one module plus one +script, not five modules. + +**3. CSS `background-image` (workspaces).** Workspace buttons come from a +single `hyprland/workspaces` module and `format-icons` takes text, not paths. +Faking the strip with eight `custom` modules would lose click-to-switch and +the active/urgent states, so instead the real module keeps its behaviour, its +labels are emptied, and each button gets an icon through +`#workspaces button:nth-child(n) { background-image: url(...) }`. + +The consequence, stated plainly: **the eight workspace icon paths live in the +stylesheet, not in the config.** Changing a workspace icon means editing CSS. + +### Why the icons cannot follow the accent + +2466 of the 5169 panel icons in `Material-Black-Plum-Suru` carry a hardcoded +`oomox` gradient (`#a08fbf` to `#5a33a3`) and seven further named gradients +each. They are not monochrome and not recolourable by CSS `color`. So icons +show their own colours and do not track the scheme, which is the intended +behaviour here: they match every other application on the desktop. + +The two icons this repo ships are the exception. `slackware.svg` (from +Simple Icons) and the two flags are single-path monochrome, so `slackware.svg` +is tinted with the udt accent by a one-line `fill` substitution at install +time. The flags keep their national colours. + +## Repository layout + +Modular, one file per module, mirroring the current setup: + + config.jsonc entry point, `include`s every module file + modules/ + clock.jsonc clock#date and clock#time + hyprland/ + workspaces.jsonc + language.jsonc + custom/ + launcher.jsonc + vmdot.jsonc + extras/ + taskbar.jsonc + tray.jsonc + wireplumber.jsonc + image/ + idle_inhibitor.jsonc presentation mode, via `image` + styles/ + style.css entry point, imports the rest in order + theme.css GENERATED by udt-palette, gitignored + fonts.css + global.css #waybar, tooltip, button base rules + pills.css the cluster geometry and transitions + modules.css per-module rules, including workspace icons + icons/ + slackware.svg carries @ACCENT@, substituted on install + flag-it.svg + flag-gb.svg + bin/ + wb-icon state to icon path, for the `image` modules + vms_dots.sh moved here from ~/bin, symlinked back + privacy_dots.sh moved here from ~/bin, symlinked back + install.sh + LICENSE + README.md + +Wildcard `include` is broken in waybar 0.14, so `config.jsonc` lists every +module file explicitly. That is a waybar limitation, not a choice. + +## Palette integration + +This repo is a **consumer** of udt: it holds the bar, udt holds the colours. + +`styles/theme.css` is generated by `udt-palette` and gitignored, exactly like +every other generated file in that repo. Generating it needs two changes on +the udt side: + +**A new `[waybar]` role block.** The current one names `temperature`, +`memory`, `cpu`, `backlight` and `battery`, none of which exist on this bar. +The new roles are `launcher`, `workspaces`, `vmdots`, `taskbar`, `volume`, +`tray`, `present`, `lang`, `date`, `time`, plus the existing `main_*`, +`hover_*` and `outline`. + +**Added to all nine `roles-*.conf`.** `udt-palette --selftest` enforces that +every scheme defines the same role set, so a role added to one file and not +the rest fails the check. That is the check doing its job. + +Dot colours come from the existing `[state]` roles: `success` for a running +VM, `warning` for paused, `critical` for crashed, `info` for saved, and +`fg_faint` for shut off. Both dot scripts currently hardcode `#30D158`, +`#FFD60A`, `#FF9F0A`, `#9B32FA` and `#6E6E73`; they will read a generated +colour block instead. + +## Install + +`install.sh` follows udt's conventions: symlink what this repo owns +canonically, copy what another application owns, guard every reload. + +- Module and style files are **symlinked** into `~/.config/waybar-udt/`, so the + repo cannot drift from what is installed. +- `icons/slackware.svg` is **generated** into place with `@ACCENT@` substituted, + the same way udt generates `launcher.rasi`. +- `bin/vms_dots.sh` and `bin/privacy_dots.sh` are symlinked to `~/bin/`, + replacing the copies that live there now. **The originals are backed up + first**: they are the only copy and are not currently in any repository. +- The bar is **not** started and `~/.config/waybar` is **not** touched. Running + it is one command, printed at the end, so DP-1's bar keeps working until you + choose otherwise. + +Reload is `killall -SIGUSR2 waybar`, and `reload_style_on_change` is already on. + +## Verification + +Per udt's own rule, visual claims are measured, not reasoned about: + + grim -o DP-3 /tmp/shot.png + +`wb-icon` gets one runnable self-check: every icon name the bar asks for must +resolve in the icon theme, so a missing icon fails loudly at install time +rather than rendering as a blank pill. + +## Open questions + +1. `custom/privacy_dots` is in the keep list but was not in the ten modules you + named. On this bar or not? +2. Licence: GPLv2-only unless you say otherwise. diff --git a/bin/privacy_dots.sh b/bin/privacy_dots.sh new file mode 100755 index 0000000..2074631 --- /dev/null +++ b/bin/privacy_dots.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +# A dot per active capture: microphone, camera, location, screen sharing. +# Copyright (C) 2026 Danilo M. +# Licensed under the GNU General Public License v2 only. +# +# dependencies: pipewire (pw-dump), jq, psmisc (fuser) +# +# Shows nothing when everything is off, which is the point: the pill only +# appears when something is actually capturing. +# +# The colours come from the palette, not from here: install.sh writes +# ~/.config/waybar-udt/dots.colors from the selected udt scheme, and this +# sources it. The defaults below are only what a missing file falls back to. +set -euo pipefail + +JQ_BIN="${JQ:-jq}" +PW_DUMP_CMD="${PW_DUMP:-pw-dump}" + +# Palette. Sourced, with fallbacks, so this script works standalone. +DOT_SUCCESS="#a6da95" +DOT_WARNING="#eed49f" +DOT_CRITICAL="#ed8796" +DOT_INFO="#8bd5ca" +DOT_ACCENT="#b7bdf8" +DOT_FAINT="#6e738d" +colors="$HOME/.config/waybar-udt/dots.colors" +# shellcheck source=/dev/null +[[ -r "$colors" ]] && . "$colors" + +mic=0 +cam=0 +loc=0 +scr=0 + +mic_app="" +cam_app="" +loc_app="" +scr_app="" + +# mic & camera +if command -v "$PW_DUMP_CMD" >/dev/null 2>&1 && command -v "$JQ_BIN" >/dev/null 2>&1; then + dump="$($PW_DUMP_CMD 2>/dev/null || true)" + + mic="$( + printf '%s' "$dump" \ + | $JQ_BIN -r ' + [ .[] + | select(.type=="PipeWire:Interface:Node") + | select((.info.props."media.class"=="Audio/Source" or .info.props."media.class"=="Audio/Source/Virtual")) + | select((.info.state=="running") or (.state=="running")) + ] | (if length>0 then 1 else 0 end) + ' 2>/dev/null || echo 0 + )" + + if [[ "$mic" -eq 1 ]]; then + mic_app="$( + printf '%s' "$dump" \ + | $JQ_BIN -r ' + [ .[] + | select(.type=="PipeWire:Interface:Node") + | select((.info.props."media.class"=="Stream/Input/Audio")) + | select((.info.state=="running") or (.state=="running")) + | .info.props["node.name"] + ] | unique | join(", ") + ' 2>/dev/null || echo "" + )" + fi + + if command -v fuser >/dev/null 2>&1; then + cam=0 + for dev in /dev/video*; do + if [ -e "$dev" ] && fuser "$dev" >/dev/null 2>&1; then + cam=1 + break + fi + done + else + cam=0 + fi + + if command -v fuser >/dev/null 2>&1; then + for dev in /dev/video*; do + if [ -e "$dev" ] && fuser "$dev" >/dev/null 2>&1; then + pids=$(fuser "$dev" 2>/dev/null) + for pid in $pids; do + pname=$(ps -p "$pid" -o comm=) + if [[ -n "$pname" ]]; then + cam_app+="$pname, " + fi + done + fi + done + cam_app="${cam_app%, }" + fi + +fi + +# location +if command -v gdbus >/dev/null 2>&1; then + if pids=$(pgrep -x geoclue); then + loc=1 + for pid in $pids; do + pname=$(ps -p "$pid" -o comm=) + [[ -n "$pname" ]] && loc_app+="$pname, " + done + loc_app="${loc_app%, }" + else + loc=0 + fi +fi + +# screen sharing +if command -v "$PW_DUMP_CMD" >/dev/null 2>&1 && command -v "$JQ_BIN" >/dev/null 2>&1; then + if [[ -z "${dump:-}" ]]; then + dump="$($PW_DUMP_CMD 2>/dev/null || true)" + fi + + scr="$( + printf '%s' "$dump" \ + | $JQ_BIN -e ' + [ .[] + | select(.info?.props?) + | select( + (.info.props["media.name"]? // "") + | test("^(xdph-streaming|gsr-default)") + ) + ] + | (if length > 0 then true else false end) + ' >/dev/null && echo 1 || echo 0 + )" +fi + +if [[ "$scr" -eq 1 ]]; then + scr_app="$( + printf '%s' "$dump" \ + | $JQ_BIN -r ' + [ .[] + | select(.type=="PipeWire:Interface:Node") + | select((.info.props."media.class"=="Stream/Input/Video") or (.info.props."media.name"=="gsr-default_output")) + | select((.info.state=="running") or (.state=="running")) + | .info.props["media.name"] + ] | unique | join(", ") + ' 2>/dev/null || echo "" + )" +fi + +# output. Capture is a state worth noticing, so these are the alert roles: +# the microphone is the quiet one, the camera and the screen are not. +dot() { + local on="$1" color="$2" + if [[ "$on" -eq 1 ]]; then + printf '' "$color" + else + printf '' + fi +} + +dots=() +mic_dot="$(dot "$mic" "$DOT_SUCCESS")"; [[ -n "$mic_dot" ]] && dots+=("$mic_dot") +cam_dot="$(dot "$cam" "$DOT_WARNING")"; [[ -n "$cam_dot" ]] && dots+=("$cam_dot") +loc_dot="$(dot "$loc" "$DOT_INFO")"; [[ -n "$loc_dot" ]] && dots+=("$loc_dot") +scr_dot="$(dot "$scr" "$DOT_CRITICAL")"; [[ -n "$scr_dot" ]] && dots+=("$scr_dot") + +text="${dots[*]}" + +if [[ -n "$mic_app" ]]; then + mic_status="Mic: $mic_app" +else + mic_status="Mic: off" +fi + +if [[ -n "$cam_app" ]]; then + cam_status="Cam: $cam_app" +else + cam_status="Cam: off" +fi + +if [[ -n "$loc_app" ]]; then + loc_status="Location: $loc_app" +else + loc_status="Location: off" +fi + +if [[ -n "$scr_app" ]]; then + scr_status="Screen sharing: $scr_app" +else + scr_status="Screen sharing: off" +fi + +tooltip="$mic_status | $cam_status | $loc_status | $scr_status" + +classes="privacydot" +[[ $mic -eq 1 ]] && classes="$classes mic-on" || classes="$classes mic-off" +[[ $cam -eq 1 ]] && classes="$classes cam-on" || classes="$classes cam-off" +[[ $loc -eq 1 ]] && classes="$classes loc-on" || classes="$classes loc-off" +[[ $scr -eq 1 ]] && classes="$classes scr-on" || classes="$classes scr-off" + +$JQ_BIN -c -n --arg text "$text" --arg tooltip "$tooltip" --arg class "$classes" \ + '{text:$text, tooltip:$tooltip, class:$class}' diff --git a/bin/vms_dots.sh b/bin/vms_dots.sh new file mode 100755 index 0000000..bf95070 --- /dev/null +++ b/bin/vms_dots.sh @@ -0,0 +1,89 @@ +#! /bin/bash +# One dot per defined VM, coloured by state. +# Copyright (C) 2026 Danilo M. +# Licensed under the GNU General Public License v2 only. +# +# dependencies: libvirt (virsh), jq +# +# The colours come from the palette, not from here: install.sh writes +# ~/.config/waybar-udt/dots.colors from the selected udt scheme, and this +# sources it. The defaults below are only what a missing file falls back to, +# so the bar still draws if the theme has not been installed yet. +set -euo pipefail + +JQ_BIN="${JQ:-jq}" + +# Palette. Sourced, with fallbacks, so this script works standalone. +DOT_SUCCESS="#a6da95" +DOT_WARNING="#eed49f" +DOT_CRITICAL="#ed8796" +DOT_INFO="#8bd5ca" +DOT_ACCENT="#b7bdf8" +DOT_FAINT="#6e738d" +colors="$HOME/.config/waybar-udt/dots.colors" +# shellcheck source=/dev/null +[[ -r "$colors" ]] && . "$colors" + +# Require virsh +if ! command -v virsh >/dev/null 2>&1; then + $JQ_BIN -cn '{text: "", tooltip: "virsh not found", class: "vmdot"}' + exit 0 +fi + +# Collect all defined VMs and their states. +# virsh list --all columns: Id Name State (state may be multi-word, e.g. "shut off") +declare -A vm_states +while IFS= read -r line; do + # Skip header lines and separator lines + [[ "$line" =~ ^[[:space:]]*Id[[:space:]] ]] && continue + [[ "$line" =~ ^[-[:space:]]+$ ]] && continue + [[ -z "${line// }" ]] && continue + + # Fields: id (may be "-"), name, state (rest of line) + read -r _id name state_rest <<< "$line" + vm_states["$name"]="$state_rest" +done < <(virsh list --all 2>/dev/null) + +if [[ ${#vm_states[@]} -eq 0 ]]; then + $JQ_BIN -cn '{text: "", tooltip: "No VMs defined", class: "vmdot"}' + exit 0 +fi + +# libvirt state → palette role. +declare -A state_color=( + ["running"]="$DOT_SUCCESS" + ["idle"]="$DOT_WARNING" # running but idle + ["paused"]="$DOT_WARNING" + ["pmsuspended"]="$DOT_ACCENT" # PM sleep / ACPI S3 + ["saved"]="$DOT_INFO" # managed save + ["crashed"]="$DOT_CRITICAL" + ["shut off"]="$DOT_FAINT" +) +fallback_color="$DOT_FAINT" # any unknown future state + +dots=() +tooltip_parts=() +classes="vmdot" + +# Sort VM names for stable ordering +mapfile -t sorted_names < <(printf '%s\n' "${!vm_states[@]}" | sort) + +for name in "${sorted_names[@]}"; do + state="${vm_states[$name]}" + color="${state_color[$state]:-$fallback_color}" + # Derive a CSS-safe class token (spaces → hyphens) + state_class="${state// /-}" + + dots+=("") + classes="$classes vm-${state_class}" + tooltip_parts+=("${name}: ${state}") +done + +text="${dots[*]}" +tooltip="$(IFS=' | '; echo "${tooltip_parts[*]}")" + +$JQ_BIN -cn \ + --arg text "$text" \ + --arg tooltip "$tooltip" \ + --arg class "$classes" \ + '{text: $text, tooltip: $tooltip, class: $class}' diff --git a/bin/wb-icon b/bin/wb-icon new file mode 100755 index 0000000..9c29c60 --- /dev/null +++ b/bin/wb-icon @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +# Resolve a module state to an icon path from the desktop's icon theme. +# Copyright (C) 2026 Danilo M. +# Licensed under the GNU General Public License v2 only. +# +# waybar's `image` module runs a command and reads "$path\n$tooltip" from its +# stdout. That is the only way a themed icon reaches a module that is not the +# taskbar or the tray, so volume, microphone and presentation mode all come +# through here. +# +# wb-icon volume # reads wpctl, prints the matching icon +# wb-icon mic +# wb-icon idle +# wb-icon --selftest # every name this bar asks for must resolve +# +# Icons keep their own colours: this desktop's icon theme draws them with +# baked-in gradients, so they are not recolourable and are not meant to be. +# Only the two icons this repo ships are monochrome. + +import subprocess +import sys + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk # noqa: E402 (must follow require_version) + +# The icon theme to draw from. Kept in one place because changing it is a +# single edit, and because --selftest needs the same value the modules use. +THEME = "Material-Black-Plum-Suru" + +SIZE = 22 + +# State to icon name. Every name here is checked by --selftest, so a theme that +# drops one fails at install time rather than rendering an empty pill. +ICONS = { + "volume": { + "muted": "audio-volume-muted", + "low": "audio-volume-low", + "medium": "audio-volume-medium", + "high": "audio-volume-high", + }, + "mic": { + "muted": "microphone-sensitivity-muted", + "on": "audio-input-microphone", + }, + "idle": { + "activated": "x-office-presentation", + "deactivated": "preferences-desktop-screensaver", + }, +} + + +def lookup(name): + """Absolute path of an icon in THEME, or None.""" + theme = Gtk.IconTheme.new() + theme.set_custom_theme(THEME) + info = theme.lookup_icon(name, SIZE, 0) + return info.get_filename() if info else None + + +def wpctl(node): + """(volume percent, muted) for a wireplumber node, or (None, False). + + wpctl prints e.g. "Volume: 0.62" or "Volume: 0.62 [MUTED]". + """ + try: + out = subprocess.run( + ["wpctl", "get-volume", node], + capture_output=True, text=True, timeout=2, + ).stdout + except (OSError, subprocess.SubprocessError): + return None, False + if "Volume:" not in out: + return None, False + muted = "MUTED" in out + try: + return round(float(out.split("Volume:")[1].split()[0]) * 100), muted + except (IndexError, ValueError): + return None, muted + + +def volume_state(pct, muted): + if muted or pct == 0: + return "muted" + if pct < 34: + return "low" + if pct < 67: + return "medium" + return "high" + + +def emit(name, tooltip): + """Print what the image module expects: a path, then a tooltip.""" + path = lookup(name) + if not path: + # An unresolved icon is a broken bar, not a warning: say so on stderr + # and print nothing, so the module stays empty rather than showing a + # stale icon. + print(f"wb-icon: no icon named {name!r} in {THEME}", file=sys.stderr) + return 1 + print(path) + print(tooltip) + return 0 + + +def selftest(): + """Every icon this bar asks for, including the ones set in CSS.""" + # The workspace icons live in styles/modules.css rather than here, because + # waybar's format-icons takes text and not paths. They are still this + # bar's icons, so they are checked here too. + workspace_icons = [ + "web-browser", "utilities-terminal", "text-editor", "network-server", + "document-edit", "applications-graphics", "internet-chat", + "input-gaming", + ] + clock_icons = ["x-office-calendar", "clock"] + + names = sorted( + {n for group in ICONS.values() for n in group.values()} + | set(workspace_icons) | set(clock_icons) + ) + missing = [n for n in names if not lookup(n)] + for n in names: + print(f" {'ok ' if n not in missing else 'MISS'} {n}") + if missing: + print(f"\n{len(missing)} icon(s) missing from {THEME}", file=sys.stderr) + return 1 + print(f"\nall {len(names)} icons resolve in {THEME}") + return 0 + + +def main(argv): + if len(argv) < 2 or argv[1] in ("-h", "--help"): + print(__doc__ or "usage: wb-icon [state]") + return 0 + + what = argv[1] + + if what == "--selftest": + return selftest() + + if what == "volume": + pct, muted = wpctl("@DEFAULT_AUDIO_SINK@") + if pct is None: + return emit(ICONS["volume"]["muted"], "Volume: unavailable") + state = volume_state(pct, muted) + label = "muted" if state == "muted" else f"{pct}%" + return emit(ICONS["volume"][state], f"Volume: {label}") + + if what == "mic": + pct, muted = wpctl("@DEFAULT_AUDIO_SOURCE@") + if pct is None: + return emit(ICONS["mic"]["muted"], "Microphone: unavailable") + state = "muted" if muted or pct == 0 else "on" + label = "muted" if state == "muted" else f"{pct}%" + return emit(ICONS["mic"][state], f"Microphone: {label}") + + if what == "idle": + state = argv[2] if len(argv) > 2 else "deactivated" + state = state if state in ICONS["idle"] else "deactivated" + on = state == "activated" + return emit(ICONS["idle"][state], + f"Presentation mode: {'on' if on else 'off'}") + + print(f"wb-icon: unknown subject {what!r}", file=sys.stderr) + return 2 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/bin/wb-idle b/bin/wb-idle new file mode 100755 index 0000000..05c4a8c --- /dev/null +++ b/bin/wb-idle @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Presentation mode: keep the screen awake, and say so with a themed icon. +# Copyright (C) 2026 Danilo M. +# Licensed under the GNU General Public License v2 only. +# +# waybar's own idle_inhibitor module draws its state with format-icons, which +# is text. This drives the same idea through hypridle and reports the state as +# an icon path for an `image` module instead. +# +# wb-idle status # print "$path\n$tooltip" for the image module +# wb-idle toggle # flip the inhibitor, then refresh the module +# +# The inhibitor is hypridle's own pause, so nothing else has to be running. +set -euo pipefail + +state_file="${XDG_RUNTIME_DIR:-/tmp}/wb-idle.state" + +active() { [[ -f "$state_file" ]]; } + +case "${1:-status}" in +toggle) + if active; then + rm -f "$state_file" + # Resume idling. Guarded: hypridle may not be running, and that is + # not an error worth failing a click over. + pkill -USR2 -x hypridle 2>/dev/null || true + else + touch "$state_file" + pkill -USR1 -x hypridle 2>/dev/null || true + fi + # Refresh the module now rather than waiting for its interval. + pkill -RTMIN+9 -x waybar 2>/dev/null || true + ;; +status) + if active; then + wb-icon idle activated + else + wb-icon idle deactivated + fi + ;; +*) + echo "usage: wb-idle " >&2 + exit 2 + ;; +esac diff --git a/bin/wb-lang b/bin/wb-lang new file mode 100755 index 0000000..5a0cd5d --- /dev/null +++ b/bin/wb-lang @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Keyboard layout, as a flag. +# Copyright (C) 2026 Danilo M. +# Licensed under the GNU General Public License v2 only. +# +# waybar's hyprland/language module styles as #language and nothing else: it +# has no per-language CSS class, so a flag cannot be selected by a selector. +# format- exists but takes text, not a path. So the flag arrives the +# same way every other icon on this bar does, through an `image` module. +# +# wb-lang # print "$path\n$tooltip" for the image module +# +# The flags live next to this bar's config because the icon theme ships 13 and +# neither Italian nor British is among them. +set -euo pipefail + +flags="$HOME/.config/waybar-udt" + +# hyprctl names the layout in full ("Italian", "English (US)"), which is what +# the keymap reports; match on that rather than on the short code, because the +# short code is not in this output. +layout="$(hyprctl devices -j 2>/dev/null \ + | python3 -c 'import json,sys +try: + d = json.load(sys.stdin) +except Exception: + sys.exit(0) +for k in d.get("keyboards", []): + if k.get("name") == "2.4g-dongle": + print(k.get("active_keymap", "")) + break' 2>/dev/null || true)" + +case "$layout" in +Italian*) flag="flag-it.svg"; name="Italian" ;; +English*) flag="flag-gb.svg"; name="English" ;; +"") flag="flag-it.svg"; name="unknown" ;; +*) flag="flag-gb.svg"; name="$layout" ;; +esac + +if [[ ! -r "$flags/$flag" ]]; then + echo "wb-lang: missing $flags/$flag" >&2 + exit 1 +fi + +echo "$flags/$flag" +echo "Layout: $name" diff --git a/config.jsonc b/config.jsonc new file mode 100644 index 0000000..9d6afae --- /dev/null +++ b/config.jsonc @@ -0,0 +1,49 @@ +// waybar-theme-udt: the bar on DP-3. +// Copyright (C) 2026 Danilo M. +// Licensed under the GNU General Public License v2 only. +// +// One file per module, included explicitly: wildcard `include` has been broken +// since waybar v0.14.0, so every module file has to be listed by name. +{ + "include": [ + "$HOME/.config/waybar-udt/modules/custom/launcher.jsonc", + "$HOME/.config/waybar-udt/modules/clock.jsonc", + "$HOME/.config/waybar-udt/modules/hyprland/workspaces.jsonc", + "$HOME/.config/waybar-udt/modules/custom/vmdot.jsonc", + "$HOME/.config/waybar-udt/modules/extras/taskbar.jsonc", + "$HOME/.config/waybar-udt/modules/custom/privacy_dots.jsonc", + "$HOME/.config/waybar-udt/modules/image/volume.jsonc", + "$HOME/.config/waybar-udt/modules/extras/tray.jsonc", + "$HOME/.config/waybar-udt/modules/image/idle_inhibitor.jsonc", + "$HOME/.config/waybar-udt/modules/image/language.jsonc", + ], + + // Clusters, not a continuous strip: the stylesheet welds neighbours + // together and leaves gaps between groups. + "modules-left": [ + "custom/launcher", + "clock#date", + "clock#time", + "hyprland/workspaces", + "custom/vmdot" + ], + "modules-center": [ + "wlr/taskbar" + ], + "modules-right": [ + "custom/privacy_dots", + "group/volume", + "tray", + "image#idle", + "image#lang" + ], + + "layer": "top", + "output": "DP-3", + "height": 0, + "width": 0, + "margin": 0, + "spacing": 0, + "mode": "dock", + "reload_style_on_change": true +} diff --git a/icons/flag-gb.svg b/icons/flag-gb.svg new file mode 100644 index 0000000..7991383 --- /dev/null +++ b/icons/flag-gb.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/flag-it.svg b/icons/flag-it.svg new file mode 100644 index 0000000..20a8bfd --- /dev/null +++ b/icons/flag-it.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/slackware.svg b/icons/slackware.svg new file mode 100644 index 0000000..c0f42cf --- /dev/null +++ b/icons/slackware.svg @@ -0,0 +1,5 @@ + + + + diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..2561bcc --- /dev/null +++ b/install.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# Install waybar-theme-udt by symlink, so the repo stays canonical. +# Copyright (C) 2026 Danilo M. +# Licensed under the GNU General Public License v2 only. +# +# This installs a SECOND bar, at ~/.config/waybar-udt/. The existing bar at +# ~/.config/waybar/ is left alone, so DP-1 keeps working while this one is +# tried on DP-3. Nothing is started: the command to run it is printed at the +# end. +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +target="$HOME/.config/waybar-udt" +udt="$(cd "$repo/../unified-desktop-theme" 2>/dev/null && pwd || true)" +icons="$HOME/.local/share/icons/Material-Black-Plum-Suru" + +mkdir -p "$target/modules/hyprland" "$target/modules/custom" \ + "$target/modules/extras" "$target/modules/image" \ + "$target/styles" "$HOME/bin" + +# ---------------------------------------------------------------- colours -- +# The palette is udt's, not this repo's. Generating it needs the sibling +# checkout, so a missing one is a clear failure rather than a bar with no +# colours: every stylesheet below names @main-bg and friends. +if [ -z "$udt" ]; then + echo "error: unified-desktop-theme not found next to this repo" >&2 + echo " expected at $repo/../unified-desktop-theme" >&2 + exit 1 +fi +"$udt/bin/udt-palette" +install -Dm644 "$udt/templates/waybar/theme.css" "$target/styles/theme.css" + +# The dot scripts are bash, not CSS, so they cannot read @define-color. Write +# the handful of colours they need as a shell fragment they source. +# +# The values are read back out of the stylesheet udt-palette just generated, +# because that is the only place they exist in a form this script can see: +# udt-palette renders files, it does not answer queries about single roles. +# +# Pango wants a colour it can parse, and the generated file mixes #rrggbb with +# rgb()/rgba() spellings, so each is normalised to hex on the way out. +scheme="$(sed -n 's/^scheme *= *//p' "$HOME/.config/udt/roles.conf")" + +css_color() { + # Last definition wins, matching how GTK resolves a redefined colour. + sed -n "s/^@define-color $1 *\(.*\);\$/\1/p" "$target/styles/theme.css" | tail -1 +} + +to_hex() { + python3 -c ' +import re, sys +v = sys.argv[1].strip() +m = re.match(r"rgba?\(([^)]*)\)", v) +if m: + p = [x.strip() for x in m.group(1).split(",")] + print("#%02x%02x%02x" % tuple(int(float(c)) for c in p[:3])) +else: + print(v) +' "$2" +} + +{ + echo "# Generated by waybar-theme-udt/install.sh from the $scheme scheme." + echo "# Sourced by vms_dots.sh and privacy_dots.sh. Do not edit." + # The generated stylesheet carries no success/info roles, so the dots use + # the palette names it does define. green/teal/mauve are role-resolved by + # udt-palette, so they still follow the scheme. + for pair in "SUCCESS:green" "WARNING:warning" "CRITICAL:critical" \ + "INFO:teal" "ACCENT:mauve" "FAINT:overlay0"; do + var="${pair%%:*}" + raw="$(css_color "${pair##*:}")" + [ -n "$raw" ] && printf 'DOT_%s="%s"\n' "$var" "$(to_hex "$var" "$raw")" + done +} > "$target/dots.colors" + +# ------------------------------------------------------------------ files -- +# Config and modules are symlinked: this repo owns them outright, so linking +# means the installed bar cannot drift from what is committed. +ln -sfn "$repo/config.jsonc" "$target/config.jsonc" +for f in modules/clock.jsonc \ + modules/hyprland/workspaces.jsonc \ + modules/custom/launcher.jsonc modules/custom/vmdot.jsonc \ + modules/custom/privacy_dots.jsonc \ + modules/extras/taskbar.jsonc modules/extras/tray.jsonc \ + modules/image/volume.jsonc modules/image/idle_inhibitor.jsonc \ + modules/image/language.jsonc; do + ln -sfn "$repo/$f" "$target/$f" +done + +for f in style.css fonts.css global.css pills.css; do + ln -sfn "$repo/styles/$f" "$target/styles/$f" +done + +# modules.css is generated rather than linked: it carries the icon theme's +# absolute path, which cannot be committed, and the accent-tinted launcher. +sed -e "s|@ICONS@|$icons|g" -e "s|@CONFIG@|$target|g" \ + "$repo/styles/modules.css" > "$target/styles/modules.css" + +# The flags ship as they are; the Slackware mark is monochrome, so it takes +# the accent. Read from the generated palette rather than restated here. +accent="$(sed -n 's/^@define-color accent *\(.*\);$/\1/p' "$target/styles/theme.css" | head -1)" +accent="${accent:-#b7bdf8}" +sed "s|@ACCENT@|$accent|" "$repo/icons/slackware.svg" > "$target/slackware.svg" +install -Dm644 "$repo/icons/flag-it.svg" "$target/flag-it.svg" +install -Dm644 "$repo/icons/flag-gb.svg" "$target/flag-gb.svg" + +# ---------------------------------------------------------------- scripts -- +# The two dot scripts used to live only in ~/bin, in no repository at all. +# They are this repo's now, and ~/bin gets links. Any real file found there is +# kept: it is the only copy of whatever was running before. +for s in vms_dots.sh privacy_dots.sh; do + if [ -f "$HOME/bin/$s" ] && [ ! -L "$HOME/bin/$s" ]; then + mv "$HOME/bin/$s" "$HOME/bin/$s.pre-udt" + echo "kept your $s as ~/bin/$s.pre-udt" + fi + ln -sfn "$repo/bin/$s" "$HOME/bin/$s" +done +ln -sfn "$repo/bin/wb-icon" "$HOME/bin/wb-icon" +ln -sfn "$repo/bin/wb-idle" "$HOME/bin/wb-idle" +ln -sfn "$repo/bin/wb-lang" "$HOME/bin/wb-lang" + +# ----------------------------------------------------------------- checks -- +# A missing icon renders as an empty pill and nothing says why, so the names +# are checked here, while someone is watching. +"$repo/bin/wb-icon" --selftest + +echo +echo "installed to $target" +echo "run it with: waybar -c $target/config.jsonc -s $target/styles/style.css" +echo "your existing bar on DP-1 is untouched." diff --git a/modules/clock.jsonc b/modules/clock.jsonc new file mode 100644 index 0000000..1c99b9a --- /dev/null +++ b/modules/clock.jsonc @@ -0,0 +1,35 @@ +// Date and time. The one pair that stays text: an icon cannot say 14:32. +// The icon sits before the text as a CSS background, which is what makes +// these two match the icon-only pills around them. +{ + "clock#date": { + "format": "{:%a %d/%m}", + "min-length": 9, + "tooltip-format": "{calendar}", + "calendar": { + "mode": "year", + "mode-mon-col": 3, + "weeks-pos": "right", + "on-scroll": 1, + // Colours here are Pango markup inside a tooltip, which CSS + // cannot reach, so they are the only colours in this repo that + // do not come from the palette. + "format": { + "months": "{}", + "days": "{}", + "weeks": "W{}", + "weekdays": "{}", + "today": "{}" + } + }, + "actions": { + "on-click": "mode" + } + }, + + "clock#time": { + "format": "{:%H:%M}", + "min-length": 5, + "tooltip-format": "Standard Time: {:%I:%M %p}" + } +} diff --git a/modules/custom/launcher.jsonc b/modules/custom/launcher.jsonc new file mode 100644 index 0000000..56f61fb --- /dev/null +++ b/modules/custom/launcher.jsonc @@ -0,0 +1,13 @@ +// Opens the quickshell desktop drawer. A static button: no "exec", so it +// cannot show whether the drawer is open, which would need the shell to feed +// waybar. +// +// The Slackware mark is a CSS background image, tinted with the udt accent at +// install time. It is the one always-coloured pill on the bar. +{ + "custom/launcher": { + "format": "", + "tooltip": false, + "on-click": "qs -p $HOME/Programming/GIT/quickshell/desktop ipc call drawer toggle" + } +} diff --git a/modules/custom/privacy_dots.jsonc b/modules/custom/privacy_dots.jsonc new file mode 100644 index 0000000..30d86b3 --- /dev/null +++ b/modules/custom/privacy_dots.jsonc @@ -0,0 +1,12 @@ +// A dot per active capture: microphone, camera, location, screen sharing. +// Shows nothing when everything is off, which is the point. The script is in +// this repo and symlinked into ~/bin, so the colours follow the palette. +{ + "custom/privacy_dots": { + "exec": "$HOME/bin/privacy_dots.sh", + "interval": 3, + "return-type": "json", + "format": "{}", + "tooltip": true + } +} diff --git a/modules/custom/vmdot.jsonc b/modules/custom/vmdot.jsonc new file mode 100644 index 0000000..1955a6e --- /dev/null +++ b/modules/custom/vmdot.jsonc @@ -0,0 +1,10 @@ +// One dot per defined VM, coloured by state. The script is in this repo and +// symlinked into ~/bin, so the colours follow the palette. +{ + "custom/vmdot": { + "exec": "$HOME/bin/vms_dots.sh", + "interval": 2, + "return-type": "json", + "tooltip": true + } +} diff --git a/modules/extras/taskbar.jsonc b/modules/extras/taskbar.jsonc new file mode 100644 index 0000000..8c9afde --- /dev/null +++ b/modules/extras/taskbar.jsonc @@ -0,0 +1,18 @@ +// Open applications, drawn with each application's own icon. +// +// icon-theme used to name Papirus, which is not installed on this machine, so +// GTK fell back silently. It now names the theme the rest of the desktop uses. +{ + "wlr/taskbar": { + "all-outputs": false, + "format": "{icon}", + "icon-theme": "Material-Black-Plum-Suru", + "icon-size": 20, + "tooltip": true, + "tooltip-format": "{title}", + "on-click": "activate", + "on-click-middle": "close", + "ignore-list": ["kitty-scratchpad"], + "cursor": true + } +} diff --git a/modules/extras/tray.jsonc b/modules/extras/tray.jsonc new file mode 100644 index 0000000..32ec0bf --- /dev/null +++ b/modules/extras/tray.jsonc @@ -0,0 +1,8 @@ +// System tray. Icons come from the applications themselves. +{ + "tray": { + "icon-size": 16, + "spacing": 12, + "cursor": true + } +} diff --git a/modules/hyprland/workspaces.jsonc b/modules/hyprland/workspaces.jsonc new file mode 100644 index 0000000..f32d497 --- /dev/null +++ b/modules/hyprland/workspaces.jsonc @@ -0,0 +1,26 @@ +// Workspaces. +// +// The icons are NOT here. waybar's format-icons takes text, not paths, so +// eight themed icons can only arrive as CSS background images; see +// styles/modules.css. The label is emptied rather than removed, because an +// empty label still gives each button its box to draw the icon in. +// +// Faking this with eight custom modules would have put the icons in config, +// at the cost of click-to-switch and the active/urgent states. Keeping the +// real module and moving the icons to CSS keeps the behaviour. +{ + "hyprland/workspaces": { + "format": "{icon}", + "format-icons": { + "1": "", "2": "", "3": "", "4": "", + "5": "", "6": "", "7": "", "8": "", + "urgent": "", "focused": "", "default": "" + }, + "persistent-workspaces": { + "*": [1, 2, 3, 4, 5, 6, 7, 8] + }, + "on-scroll-up": "hyprctl dispatch workspace +1", + "on-scroll-down": "hyprctl dispatch workspace -1", + "cursor": true + } +} diff --git a/modules/image/idle_inhibitor.jsonc b/modules/image/idle_inhibitor.jsonc new file mode 100644 index 0000000..e026be8 --- /dev/null +++ b/modules/image/idle_inhibitor.jsonc @@ -0,0 +1,15 @@ +// Presentation mode. +// +// waybar's own idle_inhibitor module draws its state with format-icons, which +// is text, so this is an `image` module driving the same inhibitor through +// hyprland. The state lives in a file because the icon has to be resolved by +// a separate process from the one that toggles it. +{ + "image#idle": { + "exec": "$HOME/bin/wb-idle status", + "size": 22, + "interval": 5, + "signal": 9, + "on-click": "$HOME/bin/wb-idle toggle" + } +} diff --git a/modules/image/language.jsonc b/modules/image/language.jsonc new file mode 100644 index 0000000..48343b7 --- /dev/null +++ b/modules/image/language.jsonc @@ -0,0 +1,18 @@ +// Keyboard layout, drawn as a flag. +// +// This is an `image` module rather than hyprland/language, because that module +// styles as #language and nothing else: it has no per-language CSS class, so a +// flag cannot be picked by a selector, and its format- takes text rather +// than a path. wb-lang reads the layout from hyprctl and prints the flag. +// +// The two flags are shipped by this repo: the icon theme has 13 and neither +// Italian nor British is among them. +{ + "image#lang": { + "exec": "$HOME/bin/wb-lang", + "size": 20, + "interval": 2, + "signal": 10, + "tooltip": true + } +} diff --git a/modules/image/volume.jsonc b/modules/image/volume.jsonc new file mode 100644 index 0000000..1788643 --- /dev/null +++ b/modules/image/volume.jsonc @@ -0,0 +1,44 @@ +// Volume, as a drawer: the output icon is always visible and the microphone +// slides out on hover, the same shape as the current bar's wireplumber group. +// +// These are `image` modules rather than wireplumber ones because wireplumber +// draws its state with format-icons, which is text. `image` runs a command and +// reads a path, so wb-icon resolves the state to a themed icon instead. The +// cost is that the percentage is in the tooltip rather than on the bar, which +// is the trade an icons-only bar makes. +// +// signal 8 is raised by volume.sh after every change, so the icon updates on +// the keypress rather than up to two seconds later. +{ + "group/volume": { + "orientation": "horizontal", + "modules": [ + "image#volume", + "image#mic" + ], + "drawer": { + "transition-left-to-right": false + } + }, + + "image#volume": { + "exec": "$HOME/bin/wb-icon volume", + "size": 22, + "interval": 2, + "signal": 8, + "on-click": "$HOME/.config/waybar/scripts/volume.sh output mute", + "on-click-right": "pavucontrol-qt", + "on-scroll-up": "$HOME/.config/waybar/scripts/volume.sh output raise", + "on-scroll-down": "$HOME/.config/waybar/scripts/volume.sh output lower" + }, + + "image#mic": { + "exec": "$HOME/bin/wb-icon mic", + "size": 22, + "interval": 2, + "signal": 8, + "on-click": "$HOME/.config/waybar/scripts/volume.sh input mute", + "on-scroll-up": "$HOME/.config/waybar/scripts/volume.sh input raise", + "on-scroll-down": "$HOME/.config/waybar/scripts/volume.sh input lower" + } +} diff --git a/styles/fonts.css b/styles/fonts.css new file mode 100644 index 0000000..8cd59ce --- /dev/null +++ b/styles/fonts.css @@ -0,0 +1,19 @@ +/* Fonts. + * + * Only three modules show text at all on this bar: the clock pair and the + * tooltips. Everything else is an icon, which is the point of the theme. + * + * Inconsolata is the desktop's monospace face, and monospace is deliberate + * for a clock: proportional digits jitter as the minute changes. + */ + +* { + font-family: "Inconsolata Nerd Font Mono", monospace; + font-weight: bold; + font-size: 16px; +} + +tooltip label { + font-weight: normal; + font-size: 14px; +} diff --git a/styles/global.css b/styles/global.css new file mode 100644 index 0000000..9886761 --- /dev/null +++ b/styles/global.css @@ -0,0 +1,31 @@ +/* The bar itself, and the chrome that is not a pill. + * + * The bar is transparent: the pills are the only thing that draws, which is + * what makes them read as separate capsules over the wallpaper. + */ + +window#waybar { + background: transparent; + color: @main-fg; +} + +/* GTK draws a button background by default, and it shows through the pill's + * own rounded corners as square shoulders. */ +button { + background: transparent; + border: none; + box-shadow: none; + text-shadow: none; + min-height: 0; +} + +tooltip { + background: @main-bg; + border: 1px solid @outline; + border-radius: 8px; +} + +tooltip label { + color: @main-fg; + padding: 2px; +} diff --git a/styles/modules.css b/styles/modules.css new file mode 100644 index 0000000..647465f --- /dev/null +++ b/styles/modules.css @@ -0,0 +1,180 @@ +/* What is different about each module. + * + * The icons for the workspaces, the clock pair, the launcher and the language + * live here rather than in the config, because waybar's format-icons takes + * text and not paths. @ICONS@ is substituted by install.sh with the icon + * theme's absolute path, so the theme can move without editing this file. + */ + +/* ------------------------------------------------------------------ * + * Launcher: the one always-coloured pill, and the desktop's identity. * + * ------------------------------------------------------------------ */ + +#custom-launcher { + background: @accent; + background-image: url("@CONFIG@/slackware.svg"); + background-repeat: no-repeat; + background-position: center; + background-size: 18px 18px; + min-width: 22px; + padding: 2px 10px; +} + +/* The accent pill inverts on hover rather than lightening: it is already the + * brightest thing on the bar. */ +#custom-launcher:hover { + background-color: @main-bg; +} + +/* ---------------------------------------------- * + * Clock: the only text, each with a leading icon. * + * ---------------------------------------------- */ + +#clock.date, +#clock.time { + background-repeat: no-repeat; + background-position: 10px center; + background-size: 16px 16px; + padding-left: 32px; +} + +#clock.date { + background-image: url("@ICONS@/mimetypes/22/x-office-calendar.svg"); +} + +#clock.time { + background-image: url("@ICONS@/categories/22/clock.svg"); +} + +/* ------------------------------------------------------------- * + * Workspaces: eight themed icons, one per button, set by CSS. * + * * + * The module's labels are empty (see the module config), so each * + * button is an icon-sized box and nothing else. * + * ------------------------------------------------------------- */ + +#workspaces button { + background-repeat: no-repeat; + background-position: center; + background-size: 18px 18px; + min-width: 26px; + min-height: 26px; + margin: 0 1px; + border-radius: 10px; + /* Dim by default; the active workspace is the one at full strength. */ + opacity: 0.45; + transition: opacity 0.3s ease, background-color 0.3s ease; +} + +#workspaces button:hover { + background-color: @hover-bg; + opacity: 0.8; +} + +#workspaces button.active, +#workspaces button.focused { + background-color: @hover-bg; + opacity: 1; +} + +#workspaces button.urgent { + background-color: @critical; + opacity: 1; +} + +/* The eight. Generic names, so the bar does not lie when the application on a + * workspace changes. */ +#workspaces button:nth-child(1) { background-image: url("@ICONS@/categories/22/web-browser.svg"); } +#workspaces button:nth-child(2) { background-image: url("@ICONS@/categories/22/utilities-terminal.svg"); } +#workspaces button:nth-child(3) { background-image: url("@ICONS@/categories/22/text-editor.svg"); } +#workspaces button:nth-child(4) { background-image: url("@ICONS@/devices/22/network-server.svg"); } +#workspaces button:nth-child(5) { background-image: url("@ICONS@/actions/22/document-edit.svg"); } +#workspaces button:nth-child(6) { background-image: url("@ICONS@/categories/22/applications-graphics.svg"); } +#workspaces button:nth-child(7) { background-image: url("@ICONS@/categories/22/internet-chat.svg"); } +#workspaces button:nth-child(8) { background-image: url("@ICONS@/devices/22/input-gaming.svg"); } + +/* ------------------------------------------------ * + * Language: a flag, shipped by this repo. * + * * + * An image module, not hyprland/language: that one * + * has no per-language CSS class, so the flag could * + * not be selected. wb-lang picks the file instead. * + * ------------------------------------------------ */ + +#image.lang { + padding: 2px 10px; + min-width: 22px; +} + +/* ------------------------------------------- * + * Dots: coloured by their scripts, not by CSS. * + * ------------------------------------------- */ + +/* The privacy pill only exists while something is capturing: an empty label + * would otherwise leave a bare capsule sitting on the bar. */ +#custom-privacy_dots { + padding: 2px 10px; +} + +#custom-privacy_dots.mic-off.cam-off.loc-off.scr-off { + padding: 0; + margin: 0; + opacity: 0; +} + +#custom-vmdot { + padding: 2px 10px; +} + +/* ---------------------------------------- * + * Volume: a drawer, the microphone hidden. * + * ---------------------------------------- */ + +#volume { + padding: 2px 8px; +} + +#volume image { + padding: 0 4px; +} + +/* --------------------------------------------------------- * + * Taskbar: application icons, with the idea2 underline. * + * --------------------------------------------------------- */ + +#taskbar button { + background: transparent; + padding: 0 8px; + margin: 0 1px; + border-bottom: 3px solid transparent; + transition: border-bottom-color 0.3s ease-out, background-color 0.3s ease; +} + +#taskbar button:hover { + background: @hover-bg; + border-bottom-color: @accent; +} + +#taskbar button.active { + border-bottom-color: @accent; +} + +/* --------------------------- * + * Tray and presentation mode. * + * --------------------------- */ + +#tray { + padding: 2px 10px; +} + +#tray menu { + background: @main-bg; + color: @main-fg; + border: 1px solid @outline; + border-radius: 8px; +} + +#image.idle { + padding: 2px 10px; + min-width: 22px; +} diff --git a/styles/pills.css b/styles/pills.css new file mode 100644 index 0000000..83e5d36 --- /dev/null +++ b/styles/pills.css @@ -0,0 +1,102 @@ +/* The pills, and the way they move. + * + * Geometry from idea1: modules are rounded capsules grouped into clusters, + * with gaps between clusters and none inside one, so the bar reads as a few + * objects rather than a continuous strip. + * + * Motion from idea2: background and colour cross-fade on hover over 0.3s. + * That is the whole effect. No keyframes anywhere, because GTK's CSS engine + * supports transitions on these properties directly and an animation would + * run forever whether or not anyone is looking at it. + */ + +/* Every pill. Shared geometry lives here exactly once; modules.css only says + * what is different about each one. */ +#custom-launcher, +#clock.date, +#clock.time, +#workspaces, +#custom-vmdot, +#taskbar, +#custom-privacy_dots, +#volume, +#tray, +#image.idle, +#image.lang { + background: @main-bg; + color: @main-fg; + border-radius: 14px; + padding: 2px 12px; + margin: 4px 0; + transition: background-color 0.3s ease, color 0.3s ease; +} + +/* Hover. The one rule that makes the bar feel alive, and the reason every + * pill declares a transition above. */ +#custom-launcher:hover, +#clock.date:hover, +#clock.time:hover, +#custom-vmdot:hover, +#custom-privacy_dots:hover, +#volume:hover, +#tray:hover, +#image.idle:hover, +#image.lang:hover { + background: @hover-bg; + color: @hover-fg; +} + +/* Clusters. + * + * Neighbours inside a cluster sit flush and share one outline; only the ends + * of a cluster round off. The gap between clusters is a margin on the last + * pill of each, so adding a module to a cluster means editing one rule. + */ + +/* Left: launcher | date time | workspaces | vm dots */ +#custom-launcher { + margin-left: 6px; + margin-right: 6px; +} + +#clock.date { + border-radius: 14px 0 0 14px; + padding-right: 8px; +} + +#clock.time { + border-radius: 0 14px 14px 0; + padding-left: 8px; + margin-right: 6px; +} + +#workspaces { + padding: 2px 6px; + margin-right: 6px; +} + +/* Right: privacy | volume | tray | presentation | language */ +#custom-privacy_dots { + margin-right: 6px; +} + +#volume { + margin-right: 6px; +} + +#tray { + margin-right: 6px; +} + +#image.idle { + margin-right: 6px; +} + +#image.lang { + margin-right: 6px; +} + +/* Center: the taskbar is one pill holding a row of buttons. */ +#taskbar { + padding: 2px 6px; +} diff --git a/styles/style.css b/styles/style.css new file mode 100644 index 0000000..d25e5fc --- /dev/null +++ b/styles/style.css @@ -0,0 +1,19 @@ +/* waybar-theme-udt: stylesheet entry point. + * Copyright (C) 2026 Danilo M. + * Licensed under the GNU General Public License v2 only. + * + * Load order matters: theme.css defines the colours every later file names, + * and modules.css overrides the shared pill geometry per module. + */ + +/* Ignore the GTK theme: waybar inherits it otherwise, and it fights the + * pills for padding and background. */ +* { + all: initial; +} + +@import "theme.css"; /* generated by udt-palette, gitignored */ +@import "fonts.css"; +@import "global.css"; +@import "pills.css"; +@import "modules.css"; -- cgit v1.2.3