feat: centralize theme palette + add waybar bluetooth module
Theme centralization: - theme/colors.json is now the single source of truth for the palette. - New theme/generate-theme.py renders, at install time, the three derived artifacts into ~/.config: colors.css (waybar/wofi @import), mako config, and hyprlock-colors.conf (hyprlang $tn_* variables). - theme/colors.css removed from the repo (now generated); install.sh calls the generator instead of the old inline mako snippet. - hyprlock.conf sources the generated color file and uses $tn_* variables instead of hardcoded rgba() values. - Generator covered by theme/test_generate_theme.py. Bluetooth: - Added waybar built-in bluetooth module (config.jsonc + style.css), with bluez/bluez-utils in packages.txt. Includes design spec and implementation plan under docs/superpowers/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
nohup.out
|
||||
mako/config
|
||||
__pycache__/
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
# Theme Centralization & Bluetooth Module Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make `theme/colors.json` the single source of truth for the desktop palette, generating all derived configs (waybar/wofi CSS, mako, hyprlock colors) from it at install time, and add a bluetooth indicator to waybar.
|
||||
|
||||
**Architecture:** A pure-function Python generator (`theme/generate-theme.py`) reads `colors.json` and renders three artifacts into `~/.config` at install time (matching how mako is already generated). `hyprlock.conf` switches to a sourced hyprlang color file using `$tn_*` variables. The hand-maintained `theme/colors.css` is deleted and becomes a generated artifact. Bluetooth uses waybar's built-in module.
|
||||
|
||||
**Tech Stack:** Python 3 (stdlib only: `json`, `os`), waybar (JSONC + GTK CSS), hyprlang (hyprlock config), bash (install.sh).
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- `theme/colors.json` — **source of truth** (unchanged; 12 base colors).
|
||||
- `theme/generate-theme.py` — **new** generator. Pure render functions + a `main()` that writes outputs. One responsibility: turn the palette into config artifacts.
|
||||
- `theme/test_generate_theme.py` — **new** test for the generator's pure functions.
|
||||
- `theme/colors.css` — **deleted from repo** (now generated into `~/.config/theme/colors.css`).
|
||||
- `install.sh` — **modified**: call the generator, drop the inline mako snippet and the colors.css symlink.
|
||||
- `hyprlock/hyprlock.conf` — **modified**: `source=` the generated color file, replace hardcoded `rgba(...)` with `$tn_*` vars.
|
||||
- `waybar/config.jsonc` — **modified**: add `bluetooth` module.
|
||||
- `waybar/style.css` — **modified**: add `#bluetooth` style rules.
|
||||
- `packages.txt` — **modified**: ensure `bluez`/`bluez-utils` present.
|
||||
|
||||
Two independent features (theme: Tasks 1–6; bluetooth: Task 7). Implement and verify separately.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Generator — palette loading and hex conversion
|
||||
|
||||
**Files:**
|
||||
- Create: `theme/generate-theme.py`
|
||||
- Test: `theme/test_generate_theme.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```python
|
||||
# theme/test_generate_theme.py
|
||||
import importlib.util, os
|
||||
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"gen", os.path.join(os.path.dirname(__file__), "generate-theme.py"))
|
||||
gen = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(gen)
|
||||
|
||||
|
||||
def test_load_palette_reads_colors_json():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
assert pal["background"] == "#1d1f21"
|
||||
assert pal["blue"] == "#81a2be"
|
||||
|
||||
|
||||
def test_hex_to_hyprlock_rgba():
|
||||
assert gen.hex_to_hyprlock_rgba("#81a2be") == "rgba(81a2beff)"
|
||||
assert gen.hex_to_hyprlock_rgba("#1d1f21") == "rgba(1d1f21ff)"
|
||||
|
||||
|
||||
def test_hex_to_rgb_tuple():
|
||||
assert gen.hex_to_rgb("#1d1f21") == (29, 31, 33)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_load_palette_reads_colors_json()
|
||||
test_hex_to_hyprlock_rgba()
|
||||
test_hex_to_rgb_tuple()
|
||||
print("OK")
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: FAIL — `FileNotFoundError` / `AttributeError: module 'gen' has no attribute 'load_palette'` (generate-theme.py does not exist yet).
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
```python
|
||||
# theme/generate-theme.py
|
||||
"""Generate desktop config artifacts from theme/colors.json (single source of truth)."""
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def load_palette(path):
|
||||
return json.load(open(path))
|
||||
|
||||
|
||||
def hex_to_rgb(h):
|
||||
h = h.lstrip("#")
|
||||
return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
|
||||
|
||||
|
||||
def hex_to_hyprlock_rgba(h):
|
||||
return f"rgba({h.lstrip('#')}ff)"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: `OK`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add theme/generate-theme.py theme/test_generate_theme.py
|
||||
git commit -m "feat(theme): generator palette loading + hex conversion"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Generator — render colors.css (with alpha variants)
|
||||
|
||||
**Files:**
|
||||
- Modify: `theme/generate-theme.py`
|
||||
- Test: `theme/test_generate_theme.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Add to `theme/test_generate_theme.py` (and add the call in `__main__`):
|
||||
|
||||
```python
|
||||
def test_render_colors_css():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
css = gen.render_colors_css(pal)
|
||||
# base colors
|
||||
assert "@define-color tn-bg #1d1f21;" in css
|
||||
assert "@define-color tn-bg-alt #282a2e;" in css
|
||||
assert "@define-color tn-bg-high #373b41;" in css
|
||||
assert "@define-color tn-fg #c5c8c6;" in css
|
||||
assert "@define-color tn-fg-dim #969896;" in css
|
||||
assert "@define-color tn-fg-muted #707880;" in css
|
||||
assert "@define-color tn-blue #81a2be;" in css
|
||||
assert "@define-color tn-green #b5bd68;" in css
|
||||
assert "@define-color tn-yellow #f0c674;" in css
|
||||
assert "@define-color tn-red #cc6666;" in css
|
||||
assert "@define-color tn-magenta #b294bb;" in css
|
||||
assert "@define-color tn-cyan #8abeb7;" in css
|
||||
# alpha variants computed from background (29,31,33)
|
||||
assert "@define-color tn-bg-a90 rgba(29, 31, 33, 0.90);" in css
|
||||
assert "@define-color tn-bg-a95 rgba(29, 31, 33, 0.95);" in css
|
||||
assert "@define-color tn-bg-a96 rgba(29, 31, 33, 0.96);" in css
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: FAIL — `AttributeError: module 'gen' has no attribute 'render_colors_css'`
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Add to `theme/generate-theme.py`:
|
||||
|
||||
```python
|
||||
# (key in colors.json) -> (css variable suffix)
|
||||
_CSS_MAP = [
|
||||
("background", "bg"), ("background_alt", "bg-alt"), ("background_high", "bg-high"),
|
||||
("foreground", "fg"), ("foreground_dim", "fg-dim"), ("foreground_muted", "fg-muted"),
|
||||
("blue", "blue"), ("green", "green"), ("yellow", "yellow"),
|
||||
("red", "red"), ("magenta", "magenta"), ("cyan", "cyan"),
|
||||
]
|
||||
_ALPHA_VARIANTS = [("a90", "0.90"), ("a95", "0.95"), ("a96", "0.96")]
|
||||
|
||||
|
||||
def render_colors_css(pal):
|
||||
lines = ["/* Tomorrow Night - GENERATED from colors.json by generate-theme.py. DO NOT EDIT. */", ""]
|
||||
for key, suffix in _CSS_MAP:
|
||||
lines.append(f"@define-color tn-{suffix} {pal[key]};")
|
||||
lines.append("")
|
||||
r, g, b = hex_to_rgb(pal["background"])
|
||||
for name, alpha in _ALPHA_VARIANTS:
|
||||
lines.append(f"@define-color tn-bg-{name} rgba({r}, {g}, {b}, {alpha});")
|
||||
return "\n".join(lines) + "\n"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: `OK`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add theme/generate-theme.py theme/test_generate_theme.py
|
||||
git commit -m "feat(theme): render colors.css from palette"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Generator — render mako config
|
||||
|
||||
**Files:**
|
||||
- Modify: `theme/generate-theme.py`
|
||||
- Test: `theme/test_generate_theme.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Add to `theme/test_generate_theme.py` (and call in `__main__`):
|
||||
|
||||
```python
|
||||
def test_render_mako_config():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
cfg = gen.render_mako_config(pal)
|
||||
assert "background-color=#1d1f21" in cfg
|
||||
assert "text-color=#c5c8c6" in cfg
|
||||
assert "border-size=2" in cfg
|
||||
assert "border-color=#81a2be" in cfg
|
||||
assert "default-timeout=4000" in cfg
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: FAIL — `AttributeError: module 'gen' has no attribute 'render_mako_config'`
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Add to `theme/generate-theme.py` (preserves the exact output of the current inline install.sh snippet):
|
||||
|
||||
```python
|
||||
def render_mako_config(pal):
|
||||
return (
|
||||
f"background-color={pal['background']}\n"
|
||||
f"text-color={pal['foreground']}\n"
|
||||
"border-size=2\n"
|
||||
f"border-color={pal['blue']}\n"
|
||||
"default-timeout=4000\n"
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: `OK`
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add theme/generate-theme.py theme/test_generate_theme.py
|
||||
git commit -m "feat(theme): render mako config from palette"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Generator — render hyprlock colors + `main()` writer
|
||||
|
||||
**Files:**
|
||||
- Modify: `theme/generate-theme.py`
|
||||
- Test: `theme/test_generate_theme.py`
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
Add to `theme/test_generate_theme.py` (and call in `__main__`):
|
||||
|
||||
```python
|
||||
def test_render_hyprlock_colors():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
out = gen.render_hyprlock_colors(pal)
|
||||
assert "$tn_fg = rgba(c5c8c6ff)" in out
|
||||
assert "$tn_fg_dim = rgba(969896ff)" in out
|
||||
assert "$tn_bg_alt = rgba(282a2eff)" in out
|
||||
assert "$tn_blue = rgba(81a2beff)" in out
|
||||
assert "$tn_red = rgba(cc6666ff)" in out
|
||||
assert "$tn_green = rgba(b5bd68ff)" in out
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: FAIL — `AttributeError: module 'gen' has no attribute 'render_hyprlock_colors'`
|
||||
|
||||
- [ ] **Step 3: Write minimal implementation**
|
||||
|
||||
Add to `theme/generate-theme.py`. The `_HYPRLOCK_MAP` covers exactly the colors hyprlock.conf needs:
|
||||
|
||||
```python
|
||||
# (colors.json key) -> (hyprlock variable name)
|
||||
_HYPRLOCK_MAP = [
|
||||
("foreground", "tn_fg"), ("foreground_dim", "tn_fg_dim"),
|
||||
("background_alt", "tn_bg_alt"), ("blue", "tn_blue"),
|
||||
("red", "tn_red"), ("green", "tn_green"),
|
||||
]
|
||||
|
||||
|
||||
def render_hyprlock_colors(pal):
|
||||
lines = ["# GENERATED from colors.json by generate-theme.py. DO NOT EDIT."]
|
||||
for key, var in _HYPRLOCK_MAP:
|
||||
lines.append(f"${var} = {hex_to_hyprlock_rgba(pal[key])}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main():
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
pal = load_palette(os.path.join(here, "colors.json"))
|
||||
cfgdir = os.path.expanduser("~/.config")
|
||||
os.makedirs(os.path.join(cfgdir, "theme"), exist_ok=True)
|
||||
os.makedirs(os.path.join(cfgdir, "mako"), exist_ok=True)
|
||||
writes = {
|
||||
os.path.join(cfgdir, "theme", "colors.css"): render_colors_css(pal),
|
||||
os.path.join(cfgdir, "mako", "config"): render_mako_config(pal),
|
||||
os.path.join(cfgdir, "theme", "hyprlock-colors.conf"): render_hyprlock_colors(pal),
|
||||
}
|
||||
for path, content in writes.items():
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
print(f" generated {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py`
|
||||
Expected: `OK`
|
||||
|
||||
- [ ] **Step 5: Verify the writer produces a colors.css matching the current one**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd ~/Documents/dotfiles && python3 theme/generate-theme.py
|
||||
diff <(grep '@define-color' theme/colors.css) <(grep '@define-color' ~/.config/theme/colors.css)
|
||||
```
|
||||
Expected: no differences (the generated palette matches the committed hand-written one). If `~/.config/theme/colors.css` was a symlink to the repo file, `diff` shows nothing because they are the same file — that is fine; the symlink is removed in Task 6.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add theme/generate-theme.py theme/test_generate_theme.py
|
||||
git commit -m "feat(theme): render hyprlock colors + main() writer"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Convert hyprlock.conf to the shared palette
|
||||
|
||||
**Files:**
|
||||
- Modify: `hyprlock/hyprlock.conf` (add `source=` line; replace 7 `rgba(...)` at lines 27, 38, 50–54)
|
||||
|
||||
- [ ] **Step 1: Add the source line at the top of `hyprlock/hyprlock.conf`**
|
||||
|
||||
Insert as the very first line (before `general {`):
|
||||
|
||||
```
|
||||
source = ~/.config/theme/hyprlock-colors.conf
|
||||
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace the hardcoded colors with variables**
|
||||
|
||||
Apply these exact replacements in `hyprlock/hyprlock.conf`:
|
||||
|
||||
| Line context | From | To |
|
||||
|---|---|---|
|
||||
| label (clock) | `color = rgba(c5c8c6ff)` | `color = $tn_fg` |
|
||||
| label (date) | `color = rgba(969896ff)` | `color = $tn_fg_dim` |
|
||||
| input-field | `inner_color = rgba(282a2eff)` | `inner_color = $tn_bg_alt` |
|
||||
| input-field | `outer_color = rgba(81a2beff)` | `outer_color = $tn_blue` |
|
||||
| input-field | `font_color = rgba(c5c8c6ff)` | `font_color = $tn_fg` |
|
||||
| input-field | `fail_color = rgba(cc6666ff)` | `fail_color = $tn_red` |
|
||||
| input-field | `check_color = rgba(b5bd68ff)` | `check_color = $tn_green` |
|
||||
|
||||
- [ ] **Step 3: Generate the hyprlock color file and parse-test hyprlock**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd ~/Documents/dotfiles && python3 theme/generate-theme.py
|
||||
cat ~/.config/theme/hyprlock-colors.conf
|
||||
# Parse test: launch with no grace, watch for parse errors, kill after 2s before interacting.
|
||||
timeout 2 hyprlock --verbose 2>&1 | grep -iE 'error|invalid|fail to parse|unknown' || echo "NO PARSE ERRORS"
|
||||
```
|
||||
Expected: the color file lists the 6 `$tn_*` variables; the parse test prints `NO PARSE ERRORS`. (hyprlock will lock the screen for ~2s — unlock with your password/fingerprint after `timeout` kills it. Do this when you can attend the screen.)
|
||||
|
||||
- [ ] **Step 4: FALLBACK (only if Step 3 shows parse errors for `source=` or `$variables`)**
|
||||
|
||||
If hyprlock v0.9.5 rejects `source=`/variables, abandon the source approach for hyprlock:
|
||||
1. Revert `hyprlock/hyprlock.conf` changes: `git checkout hyprlock/hyprlock.conf`
|
||||
2. Rename it to a template: `git mv hyprlock/hyprlock.conf hyprlock/hyprlock.conf.tmpl`
|
||||
3. In `hyprlock.conf.tmpl`, replace the 7 `rgba(...)` values with `__TN_FG__`, `__TN_FG_DIM__`, `__TN_BG_ALT__`, `__TN_BLUE__`, `__TN_RED__`, `__TN_GREEN__` placeholders.
|
||||
4. Add a `render_hyprlock_conf(pal, template_str)` function to the generator that `.replace()`s each placeholder with `hex_to_hyprlock_rgba(pal[key])`, plus a test asserting no `__` placeholders remain and `rgba(c5c8c6ff)` is present.
|
||||
5. In `main()`, read the template and write the rendered file to `~/.config/hypr/hyprlock.conf`.
|
||||
6. In `install.sh` (Task 6), remove the `ln -sf .../hyprlock/hyprlock.conf` symlink (now generated).
|
||||
Document in the commit message which path was taken.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add hyprlock/hyprlock.conf
|
||||
git commit -m "feat(theme): hyprlock uses shared palette via sourced variables"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Wire generator into install.sh; remove colors.css from repo
|
||||
|
||||
**Files:**
|
||||
- Modify: `install.sh` (replace lines 32–42 inline python; remove line 29 colors.css symlink)
|
||||
- Delete: `theme/colors.css`
|
||||
|
||||
- [ ] **Step 1: Remove the inline mako python block and the colors.css symlink**
|
||||
|
||||
In `install.sh`, delete the colors.css symlink line:
|
||||
```bash
|
||||
ln -sf "$(pwd)/theme/colors.css" ~/.config/theme/colors.css
|
||||
```
|
||||
and delete the entire inline block:
|
||||
```bash
|
||||
python3 - <<'PYEOF'
|
||||
import json, os
|
||||
c = json.load(open("theme/colors.json"))
|
||||
config = f"""background-color={c['background']}
|
||||
text-color={c['foreground']}
|
||||
border-size=2
|
||||
border-color={c['blue']}
|
||||
default-timeout=4000
|
||||
"""
|
||||
open(os.path.expanduser("~/.config/mako/config"), "w").write(config)
|
||||
PYEOF
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the generator call**
|
||||
|
||||
In `install.sh`, after the `mkdir -p ~/.config/theme` line (and after the wofi/waybar symlinks), add:
|
||||
|
||||
```bash
|
||||
echo "==> Generating theme artifacts from colors.json"
|
||||
python3 "$(pwd)/theme/generate-theme.py"
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Delete the now-generated colors.css from the repo**
|
||||
|
||||
```bash
|
||||
cd ~/Documents/dotfiles && git rm theme/colors.css
|
||||
# Remove any stale symlink so generation writes a real file:
|
||||
rm -f ~/.config/theme/colors.css
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run install.sh's theming path and verify all artifacts regenerate**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd ~/Documents/dotfiles && python3 theme/generate-theme.py
|
||||
test -f ~/.config/theme/colors.css && echo "colors.css OK"
|
||||
test -f ~/.config/mako/config && echo "mako OK"
|
||||
test -f ~/.config/theme/hyprlock-colors.conf && echo "hyprlock-colors OK"
|
||||
grep -q '@define-color tn-blue #81a2be;' ~/.config/theme/colors.css && echo "palette OK"
|
||||
waybar-restart
|
||||
```
|
||||
Expected: all four `OK` lines print, and waybar restarts with colors intact (no GTK CSS parse errors in its output).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add install.sh
|
||||
git rm --cached theme/colors.css 2>/dev/null || true
|
||||
git commit -m "build(theme): generate all theme artifacts from colors.json in install.sh"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Bluetooth waybar module
|
||||
|
||||
**Files:**
|
||||
- Modify: `waybar/config.jsonc` (add `bluetooth` to `modules-right`; add module block)
|
||||
- Modify: `waybar/style.css` (add `#bluetooth` rules)
|
||||
- Modify: `packages.txt` (ensure `bluez`, `bluez-utils`)
|
||||
|
||||
- [ ] **Step 1: Ensure bluez packages are present**
|
||||
|
||||
In `packages.txt`, confirm/add (alongside the existing `blueman`):
|
||||
```
|
||||
bluez
|
||||
bluez-utils
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Ensure the bluetooth service is running (one-time, host setup)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
systemctl is-enabled bluetooth.service 2>/dev/null || sudo systemctl enable --now bluetooth.service
|
||||
systemctl is-active bluetooth.service
|
||||
```
|
||||
Expected: `active`. (The waybar module reads adapter state from the bluez daemon.)
|
||||
|
||||
- [ ] **Step 3: Add the bluetooth module to waybar config**
|
||||
|
||||
In `waybar/config.jsonc`, add `"bluetooth"` to `modules-right` immediately after `"network"`:
|
||||
```jsonc
|
||||
"network",
|
||||
"bluetooth",
|
||||
"battery",
|
||||
```
|
||||
And add this module block (e.g. after the `network` block):
|
||||
```jsonc
|
||||
"bluetooth": {
|
||||
"format": " ",
|
||||
"format-disabled": "",
|
||||
"format-off": "",
|
||||
"format-connected": " {num_connections}",
|
||||
"tooltip-format": "{controller_alias}\n{status}",
|
||||
"tooltip-format-connected": "{controller_alias}\n{num_connections} connected\n{device_enumerate}",
|
||||
"tooltip-format-enumerate-connected": "{device_alias}",
|
||||
"on-click": "blueman-manager",
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add bluetooth styling**
|
||||
|
||||
In `waybar/style.css`, add `#bluetooth` to the shared padding selector list and add state colors:
|
||||
```css
|
||||
#bluetooth {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#bluetooth.connected {
|
||||
color: @tn-blue;
|
||||
}
|
||||
|
||||
#bluetooth.disabled, #bluetooth.off {
|
||||
color: @tn-fg-muted;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify the module renders**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
waybar-restart
|
||||
```
|
||||
Expected: a bluetooth glyph appears between network and battery; clicking it opens `blueman-manager`; toggling bluetooth changes the glyph/color. No waybar errors in output.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add waybar/config.jsonc waybar/style.css packages.txt
|
||||
git commit -m "feat(waybar): add bluetooth module"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final Verification
|
||||
|
||||
- [ ] Run the generator test suite: `cd ~/Documents/dotfiles && python3 theme/test_generate_theme.py` → `OK`
|
||||
- [ ] Re-run generation and confirm idempotency: `python3 theme/generate-theme.py` twice → identical files, no errors.
|
||||
- [ ] `waybar-restart` → bar renders with correct colors + bluetooth module.
|
||||
- [ ] Open wofi (Mod+D) → styling intact.
|
||||
- [ ] Trigger a notification → mako uses palette colors.
|
||||
- [ ] Lock screen (Mod+Shift+E) → hyprlock colors correct; unlock works.
|
||||
- [ ] `grep -rn 'rgba(' hyprlock/hyprlock.conf` → no hardcoded colors remain (only `$tn_*` vars), unless the Task 5 fallback template path was taken.
|
||||
- [ ] Confirm `theme/colors.css` no longer exists in the repo: `git ls-files theme/colors.css` → empty.
|
||||
@@ -0,0 +1,141 @@
|
||||
# Theme Centralization & Bluetooth Module — Design
|
||||
|
||||
Date: 2026-06-07
|
||||
Status: Approved (pending spec review)
|
||||
|
||||
## Summary
|
||||
|
||||
Two independent enhancements to the niri dotfiles:
|
||||
|
||||
1. **Theme centralization** — collapse the duplicated color palette into a single
|
||||
source of truth (`theme/colors.json`) and generate all derived configs from it
|
||||
at install time. Bring `hyprlock` under the shared palette. Leave `alacritty`
|
||||
untouched (out of scope).
|
||||
2. **Bluetooth module** — add a waybar bluetooth indicator using the
|
||||
already-installed `blueman`/`bluez` stack.
|
||||
|
||||
## Motivation
|
||||
|
||||
`CLAUDE.md` mandates a single source of truth and warns against duplicating
|
||||
configs / hardcoding colors in multiple places. Current state violates this:
|
||||
|
||||
- `theme/colors.css` (`@define-color` palette) and `theme/colors.json` (same
|
||||
palette as JSON) **both** hand-maintain the same 12 colors. Changing a color
|
||||
requires editing both files.
|
||||
- `hyprlock/hyprlock.conf` hardcodes 7 `rgba(...)` colors, every one of which
|
||||
already exists in the palette.
|
||||
|
||||
Already-correct (not changed by this work):
|
||||
|
||||
- waybar (`waybar/style.css`) and wofi (`wofi/style.css`) `@import` `colors.css`.
|
||||
- mako config is generated from `colors.json` by `install.sh`.
|
||||
|
||||
## Scope
|
||||
|
||||
**In scope:** unify the two palette files into one source; generate `colors.css`,
|
||||
the mako config, and a hyprlock color file from it; convert `hyprlock.conf` to
|
||||
reference the shared palette; add a waybar bluetooth module.
|
||||
|
||||
**Out of scope:** alacritty terminal colors (needs a full 16-color normal+bright
|
||||
set the palette does not currently model — deferred); any change to the
|
||||
Tomorrow Night palette values themselves; GTK theming (handled by Materia).
|
||||
|
||||
## Approach
|
||||
|
||||
Chosen approach **A**: `theme/colors.json` is the single source of truth;
|
||||
everything else is generated at install time. This extends the existing Python
|
||||
generation block already in `install.sh` (which generates the mako config).
|
||||
Rejected: parsing `colors.css` as the source (fragile regex), and introducing a
|
||||
new neutral source format (YAGNI for this scope, discards working JSON+Python).
|
||||
|
||||
## Detailed Design
|
||||
|
||||
### 1. Single source of truth
|
||||
|
||||
`theme/colors.json` remains the only hand-edited palette file. It holds the 12
|
||||
base colors (already present). The hand-maintained `theme/colors.css` is
|
||||
**deleted from the repo**; it becomes a generated artifact.
|
||||
|
||||
### 2. Generator: `theme/generate-theme.py`
|
||||
|
||||
A new standalone script, invoked by `install.sh`, replacing the current inline
|
||||
Python mako snippet. Reads `theme/colors.json` and writes three artifacts
|
||||
directly into `~/.config` (written, not symlinked — matching how mako is handled
|
||||
today):
|
||||
|
||||
- **`~/.config/theme/colors.css`** — `@define-color` block for all 12 base
|
||||
colors, plus the three alpha variants (`tn-bg-a90`, `tn-bg-a95`, `tn-bg-a96`)
|
||||
computed from the `background` color at opacities 0.90 / 0.95 / 0.96. This is
|
||||
what waybar and wofi already `@import`; their files do **not** change.
|
||||
- **`~/.config/mako/config`** — same output as the current inline snippet.
|
||||
- **`~/.config/theme/hyprlock-colors.conf`** — hyprlang variable declarations,
|
||||
one per needed color, e.g. `$tn_fg = rgba(c5c8c6ff)`. Hex from `colors.json` is
|
||||
converted to hyprlock's `rgba(RRGGBBAA)` form with `AA = ff`.
|
||||
|
||||
The generator must be idempotent and safe to re-run (overwrites its outputs).
|
||||
|
||||
### 3. hyprlock.conf
|
||||
|
||||
- Add `source = ~/.config/theme/hyprlock-colors.conf` near the top.
|
||||
- Replace the 7 hardcoded `rgba(...)` values with the matching `$tn_*`
|
||||
variables. Color → variable mapping (from current values):
|
||||
- `rgba(c5c8c6ff)` → `$tn_fg` (foreground)
|
||||
- `rgba(969896ff)` → `$tn_fg_dim` (foreground_dim)
|
||||
- `rgba(282a2eff)` → `$tn_bg_alt` (background_alt)
|
||||
- `rgba(81a2beff)` → `$tn_blue` (blue)
|
||||
- `rgba(cc6666ff)` → `$tn_red` (red)
|
||||
- `rgba(b5bd68ff)` → `$tn_green` (green)
|
||||
- `hyprlock.conf` stays a hand-edited, symlinked file; only its colors come from
|
||||
the generated source file.
|
||||
|
||||
**Risk + fallback:** hyprlock v0.9.5 uses the hyprlang parser, which documents
|
||||
support for `$variables` and `source=`. This is verified by documentation, not
|
||||
empirically (no dry-run flag; launching hyprlock grabs the screen). The
|
||||
implementation plan MUST include an explicit parse test (lock once and confirm
|
||||
colors render, or launch with `--grace 0` and watch `--verbose` for parse
|
||||
errors). **Fallback if `source=`/variables misbehave:** generate the entire
|
||||
`hyprlock.conf` from a `hyprlock/hyprlock.conf.tmpl` template with color
|
||||
placeholders, instead of using `source=`. Same single-source outcome, more
|
||||
generation.
|
||||
|
||||
### 4. install.sh changes
|
||||
|
||||
- Remove the inline `python3 - <<'PYEOF' ... PYEOF` mako block.
|
||||
- Add a call to `python3 theme/generate-theme.py` after configs are linked.
|
||||
- Remove the `ln -sf .../theme/colors.css ~/.config/theme/colors.css` line
|
||||
(colors.css is now generated into that path, not symlinked).
|
||||
- Ensure `~/.config/theme` exists before generation (already created).
|
||||
|
||||
### 5. Bluetooth waybar module
|
||||
|
||||
- `waybar/config.jsonc`: add `"bluetooth"` to `modules-right` (placed next to
|
||||
`network`), with a `bluetooth` module block:
|
||||
- `format`, `format-connected`, `format-disabled` using Nerd Font glyphs.
|
||||
- `on-click`: `blueman-manager`.
|
||||
- `waybar/style.css`: add a `#bluetooth` padding rule (consistent with the other
|
||||
right-side modules) plus a connected-state accent color using a palette
|
||||
variable (e.g. `@tn-blue`).
|
||||
- Dependencies: `blueman` + `bluez`/`bluez-utils` (blueman pulls bluez). Confirm
|
||||
`packages.txt` lists what's needed; the waybar bluetooth module requires the
|
||||
`bluez` daemon running.
|
||||
|
||||
## Testing / Verification
|
||||
|
||||
- **Generator:** run `python3 theme/generate-theme.py`; diff generated
|
||||
`~/.config/theme/colors.css` against the previous hand-written file to confirm
|
||||
identical palette + alpha variants. Confirm mako config output unchanged.
|
||||
- **waybar/wofi:** restart waybar (`waybar-restart`), confirm no CSS errors and
|
||||
colors render. Open wofi, confirm styling intact.
|
||||
- **hyprlock:** parse test per the fallback note above; confirm lock screen
|
||||
colors match before/after.
|
||||
- **Bluetooth:** confirm module appears, reflects adapter state, and
|
||||
`blueman-manager` opens on click.
|
||||
- **Idempotency:** re-run `install.sh`; confirm no errors and stable output.
|
||||
|
||||
## Rollout Notes
|
||||
|
||||
- Generated files (`~/.config/theme/colors.css`, `~/.config/theme/hyprlock-colors.conf`)
|
||||
are not committed and not symlinked — consistent with the existing mako
|
||||
generation. The committed `theme/colors.css` is removed in this change.
|
||||
- The two features (theme, bluetooth) are independent and can be implemented and
|
||||
verified separately.
|
||||
@@ -1,3 +1,5 @@
|
||||
source = ~/.config/theme/hyprlock-colors.conf
|
||||
|
||||
general {
|
||||
grace = 0
|
||||
hide_cursor = true
|
||||
@@ -24,7 +26,7 @@ label {
|
||||
text = cmd[update:1000] date +"%-I:%M %p"
|
||||
font_family = JetBrains Mono Nerd Font
|
||||
font_size = 96
|
||||
color = rgba(c5c8c6ff)
|
||||
color = $tn_fg
|
||||
position = 0, 150
|
||||
halign = center
|
||||
valign = center
|
||||
@@ -35,7 +37,7 @@ label {
|
||||
text = cmd[update:60000] date +"%A, %B %-d"
|
||||
font_family = JetBrains Mono Nerd Font
|
||||
font_size = 24
|
||||
color = rgba(969896ff)
|
||||
color = $tn_fg_dim
|
||||
position = 0, 40
|
||||
halign = center
|
||||
valign = center
|
||||
@@ -47,11 +49,11 @@ input-field {
|
||||
outline_thickness = 2
|
||||
dots_size = 0.25
|
||||
dots_spacing = 0.4
|
||||
inner_color = rgba(282a2eff)
|
||||
outer_color = rgba(81a2beff)
|
||||
font_color = rgba(c5c8c6ff)
|
||||
fail_color = rgba(cc6666ff)
|
||||
check_color = rgba(b5bd68ff)
|
||||
inner_color = $tn_bg_alt
|
||||
outer_color = $tn_blue
|
||||
font_color = $tn_fg
|
||||
fail_color = $tn_red
|
||||
check_color = $tn_green
|
||||
placeholder_text = <i>Touch fingerprint or type password</i>
|
||||
fail_text = <i>$FAIL</i>
|
||||
fade_on_empty = false
|
||||
|
||||
+2
-12
@@ -26,20 +26,10 @@ ln -sf "$(pwd)/niri/config.kdl" ~/.config/niri/config.kdl
|
||||
ln -sf "$(pwd)/waybar/config.jsonc" ~/.config/waybar/config.jsonc
|
||||
ln -sf "$(pwd)/waybar/style.css" ~/.config/waybar/style.css
|
||||
mkdir -p ~/.config/theme
|
||||
ln -sf "$(pwd)/theme/colors.css" ~/.config/theme/colors.css
|
||||
echo "==> Generating theme artifacts from colors.json"
|
||||
python3 "$(pwd)/theme/generate-theme.py"
|
||||
ln -sf "$(pwd)/wofi/config" ~/.config/wofi/config
|
||||
ln -sf "$(pwd)/wofi/style.css" ~/.config/wofi/style.css
|
||||
python3 - <<'PYEOF'
|
||||
import json, os
|
||||
c = json.load(open("theme/colors.json"))
|
||||
config = f"""background-color={c['background']}
|
||||
text-color={c['foreground']}
|
||||
border-size=2
|
||||
border-color={c['blue']}
|
||||
default-timeout=4000
|
||||
"""
|
||||
open(os.path.expanduser("~/.config/mako/config"), "w").write(config)
|
||||
PYEOF
|
||||
mkdir -p ~/.config/hypr
|
||||
ln -sf "$(pwd)/hyprlock/hyprlock.conf" ~/.config/hypr/hyprlock.conf
|
||||
ln -sf "$(pwd)/fish/config.fish" ~/.config/fish/config.fish
|
||||
|
||||
@@ -24,6 +24,8 @@ greetd-regreet
|
||||
cage
|
||||
cliphist
|
||||
blueman
|
||||
bluez
|
||||
bluez-utils
|
||||
wlsunset
|
||||
pavucontrol
|
||||
nwg-look
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/* Tomorrow Night - shared color palette */
|
||||
/* @import this file from waybar, wofi CSS */
|
||||
|
||||
@define-color tn-bg #1d1f21;
|
||||
@define-color tn-bg-alt #282a2e;
|
||||
@define-color tn-bg-high #373b41;
|
||||
@define-color tn-fg #c5c8c6;
|
||||
@define-color tn-fg-dim #969896;
|
||||
@define-color tn-fg-muted #707880;
|
||||
@define-color tn-blue #81a2be;
|
||||
@define-color tn-green #b5bd68;
|
||||
@define-color tn-yellow #f0c674;
|
||||
@define-color tn-red #cc6666;
|
||||
@define-color tn-magenta #b294bb;
|
||||
@define-color tn-cyan #8abeb7;
|
||||
|
||||
/* Alpha variants */
|
||||
@define-color tn-bg-a90 rgba(29, 31, 33, 0.90);
|
||||
@define-color tn-bg-a95 rgba(29, 31, 33, 0.95);
|
||||
@define-color tn-bg-a96 rgba(29, 31, 33, 0.96);
|
||||
@@ -0,0 +1,85 @@
|
||||
# theme/generate-theme.py
|
||||
"""Generate desktop config artifacts from theme/colors.json (single source of truth)."""
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def load_palette(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def hex_to_rgb(h):
|
||||
h = h.lstrip("#")
|
||||
return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
|
||||
|
||||
|
||||
def hex_to_hyprlock_rgba(h):
|
||||
return f"rgba({h.lstrip('#')}ff)"
|
||||
|
||||
|
||||
# (key in colors.json) -> (css variable suffix)
|
||||
_CSS_MAP = [
|
||||
("background", "bg"), ("background_alt", "bg-alt"), ("background_high", "bg-high"),
|
||||
("foreground", "fg"), ("foreground_dim", "fg-dim"), ("foreground_muted", "fg-muted"),
|
||||
("blue", "blue"), ("green", "green"), ("yellow", "yellow"),
|
||||
("red", "red"), ("magenta", "magenta"), ("cyan", "cyan"),
|
||||
]
|
||||
_ALPHA_VARIANTS = [("a90", "0.90"), ("a95", "0.95"), ("a96", "0.96")]
|
||||
|
||||
|
||||
def render_mako_config(pal):
|
||||
return (
|
||||
f"background-color={pal['background']}\n"
|
||||
f"text-color={pal['foreground']}\n"
|
||||
"border-size=2\n"
|
||||
f"border-color={pal['blue']}\n"
|
||||
"default-timeout=4000\n"
|
||||
)
|
||||
|
||||
|
||||
def render_colors_css(pal):
|
||||
lines = ["/* Tomorrow Night - GENERATED from colors.json by generate-theme.py. DO NOT EDIT. */", ""]
|
||||
for key, suffix in _CSS_MAP:
|
||||
lines.append(f"@define-color tn-{suffix} {pal[key]};")
|
||||
lines.append("")
|
||||
r, g, b = hex_to_rgb(pal["background"])
|
||||
for name, alpha in _ALPHA_VARIANTS:
|
||||
lines.append(f"@define-color tn-bg-{name} rgba({r}, {g}, {b}, {alpha});")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
# (colors.json key) -> (hyprlock variable name)
|
||||
_HYPRLOCK_MAP = [
|
||||
("foreground", "tn_fg"), ("foreground_dim", "tn_fg_dim"),
|
||||
("background_alt", "tn_bg_alt"), ("blue", "tn_blue"),
|
||||
("red", "tn_red"), ("green", "tn_green"),
|
||||
]
|
||||
|
||||
|
||||
def render_hyprlock_colors(pal):
|
||||
lines = ["# GENERATED from colors.json by generate-theme.py. DO NOT EDIT."]
|
||||
for key, var in _HYPRLOCK_MAP:
|
||||
lines.append(f"${var} = {hex_to_hyprlock_rgba(pal[key])}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def main():
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
pal = load_palette(os.path.join(here, "colors.json"))
|
||||
cfgdir = os.path.expanduser("~/.config")
|
||||
os.makedirs(os.path.join(cfgdir, "theme"), exist_ok=True)
|
||||
os.makedirs(os.path.join(cfgdir, "mako"), exist_ok=True)
|
||||
writes = {
|
||||
os.path.join(cfgdir, "theme", "colors.css"): render_colors_css(pal),
|
||||
os.path.join(cfgdir, "mako", "config"): render_mako_config(pal),
|
||||
os.path.join(cfgdir, "theme", "hyprlock-colors.conf"): render_hyprlock_colors(pal),
|
||||
}
|
||||
for path, content in writes.items():
|
||||
with open(path, "w") as f:
|
||||
f.write(content)
|
||||
print(f" generated {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,75 @@
|
||||
# theme/test_generate_theme.py
|
||||
import importlib.util, os
|
||||
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"gen", os.path.join(os.path.dirname(__file__), "generate-theme.py"))
|
||||
gen = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(gen)
|
||||
|
||||
|
||||
def test_load_palette_reads_colors_json():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
assert pal["background"] == "#1d1f21"
|
||||
assert pal["blue"] == "#81a2be"
|
||||
|
||||
|
||||
def test_hex_to_hyprlock_rgba():
|
||||
assert gen.hex_to_hyprlock_rgba("#81a2be") == "rgba(81a2beff)"
|
||||
assert gen.hex_to_hyprlock_rgba("#1d1f21") == "rgba(1d1f21ff)"
|
||||
|
||||
|
||||
def test_hex_to_rgb_tuple():
|
||||
assert gen.hex_to_rgb("#1d1f21") == (29, 31, 33)
|
||||
|
||||
|
||||
def test_render_colors_css():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
css = gen.render_colors_css(pal)
|
||||
# base colors
|
||||
assert "@define-color tn-bg #1d1f21;" in css
|
||||
assert "@define-color tn-bg-alt #282a2e;" in css
|
||||
assert "@define-color tn-bg-high #373b41;" in css
|
||||
assert "@define-color tn-fg #c5c8c6;" in css
|
||||
assert "@define-color tn-fg-dim #969896;" in css
|
||||
assert "@define-color tn-fg-muted #707880;" in css
|
||||
assert "@define-color tn-blue #81a2be;" in css
|
||||
assert "@define-color tn-green #b5bd68;" in css
|
||||
assert "@define-color tn-yellow #f0c674;" in css
|
||||
assert "@define-color tn-red #cc6666;" in css
|
||||
assert "@define-color tn-magenta #b294bb;" in css
|
||||
assert "@define-color tn-cyan #8abeb7;" in css
|
||||
# alpha variants computed from background (29,31,33)
|
||||
assert "@define-color tn-bg-a90 rgba(29, 31, 33, 0.90);" in css
|
||||
assert "@define-color tn-bg-a95 rgba(29, 31, 33, 0.95);" in css
|
||||
assert "@define-color tn-bg-a96 rgba(29, 31, 33, 0.96);" in css
|
||||
|
||||
|
||||
def test_render_mako_config():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
cfg = gen.render_mako_config(pal)
|
||||
assert "background-color=#1d1f21" in cfg
|
||||
assert "text-color=#c5c8c6" in cfg
|
||||
assert "border-size=2" in cfg
|
||||
assert "border-color=#81a2be" in cfg
|
||||
assert "default-timeout=4000" in cfg
|
||||
|
||||
|
||||
def test_render_hyprlock_colors():
|
||||
pal = gen.load_palette(os.path.join(os.path.dirname(__file__), "colors.json"))
|
||||
out = gen.render_hyprlock_colors(pal)
|
||||
assert "$tn_fg = rgba(c5c8c6ff)" in out
|
||||
assert "$tn_fg_dim = rgba(969896ff)" in out
|
||||
assert "$tn_bg_alt = rgba(282a2eff)" in out
|
||||
assert "$tn_blue = rgba(81a2beff)" in out
|
||||
assert "$tn_red = rgba(cc6666ff)" in out
|
||||
assert "$tn_green = rgba(b5bd68ff)" in out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_load_palette_reads_colors_json()
|
||||
test_hex_to_hyprlock_rgba()
|
||||
test_hex_to_rgb_tuple()
|
||||
test_render_colors_css()
|
||||
test_render_mako_config()
|
||||
test_render_hyprlock_colors()
|
||||
print("OK")
|
||||
@@ -12,6 +12,7 @@
|
||||
"custom/fan-profile",
|
||||
"pulseaudio",
|
||||
"network",
|
||||
"bluetooth",
|
||||
"battery",
|
||||
"tray",
|
||||
],
|
||||
@@ -82,6 +83,17 @@
|
||||
"on-click": "networkmanager_dmenu",
|
||||
},
|
||||
|
||||
"bluetooth": {
|
||||
"format": " ",
|
||||
"format-disabled": "",
|
||||
"format-off": "",
|
||||
"format-connected": " {num_connections}",
|
||||
"tooltip-format": "{controller_alias}\n{status}",
|
||||
"tooltip-format-connected": "{controller_alias}\n{num_connections} connected\n{device_enumerate}",
|
||||
"tooltip-format-enumerate-connected": "{device_alias}",
|
||||
"on-click": "blueman-manager",
|
||||
},
|
||||
|
||||
"pulseaudio": {
|
||||
"format": " {volume}%",
|
||||
"format-muted": "",
|
||||
|
||||
@@ -56,3 +56,15 @@ window#waybar {
|
||||
#temperature.critical {
|
||||
color: @tn-red;
|
||||
}
|
||||
|
||||
#bluetooth {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#bluetooth.connected {
|
||||
color: @tn-blue;
|
||||
}
|
||||
|
||||
#bluetooth.disabled, #bluetooth.off {
|
||||
color: @tn-fg-muted;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user