diff options
| -rw-r--r-- | .gitignore | 10 | ||||
| -rw-r--r-- | LICENSE | 338 | ||||
| -rw-r--r-- | README.md | 135 | ||||
| -rw-r--r-- | SPEC.md | 208 | ||||
| -rwxr-xr-x | bin/privacy_dots.sh | 199 | ||||
| -rwxr-xr-x | bin/vms_dots.sh | 89 | ||||
| -rwxr-xr-x | bin/wb-icon | 171 | ||||
| -rwxr-xr-x | bin/wb-idle | 45 | ||||
| -rwxr-xr-x | bin/wb-lang | 46 | ||||
| -rw-r--r-- | config.jsonc | 49 | ||||
| -rw-r--r-- | icons/flag-gb.svg | 7 | ||||
| -rw-r--r-- | icons/flag-it.svg | 7 | ||||
| -rw-r--r-- | icons/slackware.svg | 5 | ||||
| -rwxr-xr-x | install.sh | 130 | ||||
| -rw-r--r-- | modules/clock.jsonc | 35 | ||||
| -rw-r--r-- | modules/custom/launcher.jsonc | 13 | ||||
| -rw-r--r-- | modules/custom/privacy_dots.jsonc | 12 | ||||
| -rw-r--r-- | modules/custom/vmdot.jsonc | 10 | ||||
| -rw-r--r-- | modules/extras/taskbar.jsonc | 18 | ||||
| -rw-r--r-- | modules/extras/tray.jsonc | 8 | ||||
| -rw-r--r-- | modules/hyprland/workspaces.jsonc | 26 | ||||
| -rw-r--r-- | modules/image/idle_inhibitor.jsonc | 15 | ||||
| -rw-r--r-- | modules/image/language.jsonc | 18 | ||||
| -rw-r--r-- | modules/image/volume.jsonc | 44 | ||||
| -rw-r--r-- | styles/fonts.css | 19 | ||||
| -rw-r--r-- | styles/global.css | 31 | ||||
| -rw-r--r-- | styles/modules.css | 180 | ||||
| -rw-r--r-- | styles/pills.css | 102 | ||||
| -rw-r--r-- | styles/style.css | 19 |
29 files changed, 1989 insertions, 0 deletions
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 @@ -0,0 +1,338 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + <https://fsf.org/> + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 <https://www.gnu.org/licenses/>. + +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. + + <signature of Moe Ghoul>, 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. @@ -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-<scheme>.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. <danix@danix.xyz> +# 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 '<span foreground="%s">●</span>' "$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. <danix@danix.xyz> +# 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+=("<span foreground=\"${color}\">●</span>") + 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. <danix@danix.xyz> +# 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 <activated|deactivated> +# 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 <volume|mic|idle|--selftest> [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. <danix@danix.xyz> +# 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 <status|toggle>" >&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. <danix@danix.xyz> +# 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-<lang> 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. <danix@danix.xyz> +// 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-gb" viewBox="0 0 640 480"> + <path fill="#012169" d="M0 0h640v480H0z"/> + <path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0z"/> + <path fill="#C8102E" d="m424 281 216 159v40L369 281zm-184 20 6 35L54 480H0zM640 0v3L391 191l2-44L590 0zM0 0l239 176h-60L0 42z"/> + <path fill="#FFF" d="M241 0v480h160V0zM0 160v160h640V160z"/> + <path fill="#C8102E" d="M0 193v96h640v-96zM273 0v480h96V0z"/> +</svg> 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 @@ +<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-it" viewBox="0 0 640 480"> + <g fill-rule="evenodd" stroke-width="1pt"> + <path fill="#fff" d="M0 0h640v480H0z"/> + <path fill="#009246" d="M0 0h213.3v480H0z"/> + <path fill="#ce2b37" d="M426.7 0H640v480H426.7z"/> + </g> +</svg> 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 @@ +<!-- Slackware logo, from Simple Icons (CC0 1.0 Universal). + @ACCENT@ is substituted by install.sh with the udt accent colour. --> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> + <path fill="@ACCENT@" d="M12.924 1.228c-.584-.01-1.251 0-1.485.027-2.46.282-4.138 1.3-4.753 2.891-.218.552-.274 1.002-.243 1.772.048 1.21.419 2.004 1.262 2.742 1.225 1.06 2.98 1.508 5.998 1.508 2.737 0 3.71.413 3.916 1.675.313 1.867-1.57 3.07-4.414 2.827-1.878-.16-3.496-.912-4.223-1.967a7.772 7.772 0 01-.355-.62c-.382-.76-.64-.978-1.176-.978-.43.005-.732.165-.918.494l-.133.24v4.03l.137.296c.165.344.4.546.744.63.35.09.794-.036 1.42-.402l.5-.29.826.185c1.82.403 2.75.523 4.065.523 1.103.005 1.548-.046 2.455-.285 1.124-.297 1.974-.785 2.717-1.57.8-.844 1.15-1.853 1.097-3.147-.069-1.628-.695-2.698-2-3.414-.96-.525-2.292-.79-4.377-.88-2.042-.086-2.794-.155-3.515-.32-.51-.12-.785-.25-1.076-.515-.653-.589-.59-1.755.136-2.482.642-.637 1.511-.928 2.774-.928 1.432.005 2.393.27 3.412.955.185.127.721.62 1.193 1.092.886.902 1.135 1.082 1.506 1.082.244 0 .59-.163.732-.344.26-.329.303-.63.303-2.2 0-1.66-.043-1.91-.377-2.282-.387-.425-.848-.42-1.75.031l-.59.297-.63-.17c-1.496-.392-2.038-.477-3.178-.504zM0 13.775v9h24v-1H1v-8z"/> +</svg> 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. <danix@danix.xyz> +# 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": "<tt>{calendar}</tt>", + "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": "<span color='#7dc4e4'><b>{}</b></span>", + "days": "<span color='#8aadf4'><b>{}</b></span>", + "weeks": "<span color='#99ffdd'><b>W{}</b></span>", + "weekdays": "<span color='#b7bdf8'><b>{}</b></span>", + "today": "<span color='#f0c6c6'><b><u>{}</u></b></span>" + } + }, + "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-<lang> 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. <danix@danix.xyz> + * 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"; |
