Compare commits

..
Author SHA1 Message Date
tri 99517bbb6c docs(readme): clone over https
SSH clones need a registered key even for a public repo, so a fresh
machine got "Permission denied (publickey)". HTTPS works anonymously.
2026-11-13 11:13:55 +07:00
tri 1ac77c4045 chore(npm): lower the release-age window to 1 day 2026-11-11 16:39:41 +07:00
tri 61e0ab126f docs(readme): clone over ssh 2026-11-10 16:49:08 +07:00
tri b6b079b8f0 docs(readme): clone from git.trivk.dev 2026-11-10 12:11:28 +07:00
tri f9b35addae feat(zsh): source zshenv.local for per-machine settings 2026-11-06 13:12:29 +07:00
tri 8425ceb709 feat(tray-icons): shrink and recolor the NetBird tray icons
NetBird sends its tray icon as a pixmap, so an icon theme can't
replace it. patch.py rewrites the 48x48 PNGs embedded in netbird-ui in
place: the glyph is scaled to 80% to get Breeze's padding and the
white variants take the panel text color (#dedede). A pacman hook
re-patches the binary after every netbird-ui upgrade, and restore.sh
puts the original back.
2026-11-06 11:20:04 +07:00
tri fa77bc2514 refactor(fastfetch): merge the mac and kde configs into common
One Fastfetch package now serves both platforms. The logo is sent
inline through the kitty graphics protocol from a pre-encoded
albedo.kitty, which works in Ghostty and Kwi alike, so the separate
kitty-direct (mac) and Konsole (kde) variants are no longer needed.
2026-11-06 11:20:04 +07:00
tri 84e96f175c chore(spectacle): mark the desktop launcher as trusted
KDE trusts a .desktop file once it is executable and starts with an
xdg-open shebang, so Plasma launches it without asking first.
2026-11-03 22:18:52 +07:00
tri 09c1e312cb feat(helium): switch to the GTK theme with KWin title buttons
Helium now takes its colors from Breeze GTK instead of a generated
theme extension, which reset the GTK theme on every launch.

- Import window_decorations.css from gtk.css so Helium, which doesn't
  load kde-gtk-config's GTK module, draws the KWin title buttons
  instead of Breeze GTK's round ones.
- Add chromium.css to give the compact layout's address bar and
  button segment the title bar color, so the tab strip is one color.
- Disable WaylandWpColorManagerV1 so KWin doesn't lift Helium's dark
  grays (40 -> 44) and the strip matches KDE title bars.
- Drop the Helium theme from build.py and the helium package.
2026-11-03 22:18:52 +07:00
tri b14133bbec Merge pull request #2 from vokhactri/refactor/add-python-type-hints
Add type hints to KDE Python build scripts
2026-11-02 15:18:44 +07:00
32 changed files with 404 additions and 371 deletions

No files matched your search

+6 -6
View File
@@ -5,15 +5,15 @@ These are my config files for macOS and KDE, managed with GNU Stow. You need `st
## Installation
```sh
git clone https://github.com/vokhactri/.dotfiles.git "$HOME/.dotfiles"
git clone https://git.trivk.dev/tri/.dotfiles.git "$HOME/.dotfiles"
cd "$HOME/.dotfiles"
```
The packages are split into three groups:
- [common/](common/README.md) has Git, Micro, mpv, npm, Starship and Zsh.
- [mac/](mac/README.md) has Ghostty and the original Fastfetch config.
- [kde/](kde/README.md) has the Darwin Breeze theme, Helium, Konsole Fastfetch, Spectacle and tray icons.
- [common/](common/README.md) has Fastfetch, Git, Micro, mpv, npm, Starship and Zsh.
- [mac/](mac/README.md) has Ghostty.
- [kde/](kde/README.md) has the Darwin Breeze theme, Helium, Spectacle and tray icons.
Install all of `common`, then the group for your platform:
@@ -32,10 +32,10 @@ You don't have to install everything. List only the packages you want:
```sh
stow -d common -t "$HOME" git zsh starship
stow -d mac -t "$HOME" ghostty
stow -d kde -t "$HOME" spectacle fastfetch
stow -d kde -t "$HOME" spectacle
```
`-d` tells Stow which group to read from. `-t` puts the symlinks in your home directory. You can mix packages from different groups. Fastfetch is the exception. Install `mac/fastfetch` or `kde/fastfetch`, never both.
`-d` tells Stow which group to read from. `-t` puts the symlinks in your home directory. You can mix packages from different groups.
### Previewing and removing
+2 -2
View File
@@ -1,6 +1,6 @@
# Common packages
Shared configuration for Git, Micro, mpv, npm, Starship, and Zsh.
Shared configuration for Fastfetch, Git, Micro, mpv, npm, Starship, and Zsh.
From the repository root, install all shared packages:
```sh
@@ -14,5 +14,5 @@ stow -d common -t "$HOME" git
stow -d common -t "$HOME" zsh starship
```
Choose any of `git`, `micro`, `mpv`, `npm`, `starship`, and `zsh`.
Choose any of `fastfetch`, `git`, `micro`, `mpv`, `npm`, `starship`, and `zsh`.
Reload Zsh with `exec zsh` after installing its configuration.
@@ -0,0 +1 @@
_Ga=T,f=100,t=d,c=20,r=10,q=2,m=1;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\_Gm=1;R+I1tGnTxpUIALupermOJgMSCAQCocgRAEVRUABcUMncKQEgIgA4GU8z+1WrBo888gjLdf/xh+gubA7cBZ8+fRpWr14No0ePhnvvvReSk5PB39/Wc1902jOKAUwTNG7QRosKsLbBpaSkUJs2mDOYVT14e/tqEYBOnTq5JACkCMByagdMIBAIBGciAJUA4KQrPQAEgV26dIl1vhs5ciScPXvWlOiR8LBMEC9vRH67du2CMWPGsEhBpUqVDJEBb53BMDAgFOJjmrESvNIkBPAxMUqRmdqDmR/Fc8YIiIsmQFEFME/u6UAgEAgEQmEFQAAAbBcc7UoVgBFI9GI0rkgVOFpGcWC8P4wQfPvtt3DrrbeyUjpblYEneEkTBNE8iBP7cGCPo7bCxSsAsFPgEDZSWK4EePrpp7Vj5GQKQEwFnCJXdBAIBAKBUJROgD+rZHK1qGkAYx8AmfTlv9/obo3lhEIUCEEg49ChQ/DOO+9Aw4YN7aIC4jqO7MWIAEsNqK16izsaICYJ4uMnJbbWPccvvvjCFQGgSAObvlD7AFAjIAKBQCAUeQ7A2yqZFLkRkBmRuzDtzu6+xKWZGMDfz5o1Czp27OhQCGBqIKVhtq65UHFGAjD6gJEIjErIfRHmzp1rNyypaIdGa9r0P2oFTCAQCARnBUBHlUxEHwBrGNwiGKMM8nhhARy927NnT10PAjk1gB0Ic1v3ZYa84hIBokSxS/5QqFa5jvZc/Pz8YNu2ba6UAcqNgIaRACAQCASCsymA8gBw3NVhQMUBo3fAGBVYv3498wnIZYTCeY8dB9EkKEcC3C0AhA8gJChce05YLnn58mXt9ThxDGQB0E54AOjjTyAQCARLine truncated
File renamed without changes.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

@@ -11,7 +11,7 @@
},
"logo": {
"type": "raw",
"source": "~/.config/fastfetch/albedo-konsole.kitty",
"source": "~/.config/fastfetch/albedo.kitty",
"width": 20,
"height": 10
},
File renamed without changes.
+17
View File
@@ -0,0 +1,17 @@
# Fastfetch
```sh
stow -d common -t "$HOME" fastfetch
```
The logo is `albedo.png`, sent inline through the kitty graphics protocol from
the pre-encoded `albedo.kitty`. This works in Ghostty and Kwi. The
`kitty-direct` logo type is not used because Kwi ignores images passed as a
file path.
After changing `albedo.png` or the logo size in `config.jsonc`, regenerate the
logo from the repository root:
```sh
python3 common/fastfetch/build.py
```
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Encode albedo.png as kitty graphics escapes for fastfetch's "raw" logo.
fastfetch's "kitty-direct" logo sends the terminal a file path (t=f), which
Kwi ignores. This file carries the PNG inline (t=d) instead, so it works in
any terminal with the kitty graphics protocol, Ghostty and Kwi included. q=2
keeps the terminal from answering. Rerun after changing the image or the
logo size.
"""
import base64
from pathlib import Path
DIR = Path(__file__).resolve().parent / ".config/fastfetch"
# Logo size in cells; keep in sync with "width" and "height" in config.jsonc.
COLS, ROWS = 20, 10
def encode(png: bytes) -> bytes:
data = base64.b64encode(png)
chunks = [data[i : i + 4096] for i in range(0, len(data), 4096)]
out = bytearray()
for n, chunk in enumerate(chunks):
more = int(n < len(chunks) - 1)
if n == 0:
keys = b"a=T,f=100,t=d,c=%d,r=%d,q=2,m=%d" % (COLS, ROWS, more)
else:
keys = b"m=%d" % more
out += b"\x1b_G" + keys + b";" + chunk + b"\x1b\\"
return bytes(out)
(DIR / "albedo.kitty").write_bytes(encode((DIR / "albedo.png").read_bytes()))
+2 -2
View File
@@ -1,2 +1,2 @@
min-release-age=7 ; 7 days for npm
minimum-release-age=10080 ; 7 days in minutes for pnpm
min-release-age=1 ; 1 day for npm
minimum-release-age=1440 ; 1 day in minutes for pnpm
+3
View File
@@ -5,3 +5,6 @@ export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
XDG_BIN_HOME="${XDG_BIN_HOME:-$HOME/.local/bin}"
ZDOTDIR="$XDG_CONFIG_HOME/zsh"
# Per-machine settings that stay out of the repo, like PATH entries for a VPS.
[ -r "$XDG_CONFIG_HOME/zshenv.local" ] && . "$XDG_CONFIG_HOME/zshenv.local"
+7 -9
View File
@@ -9,16 +9,15 @@ From the repository root, install all KDE packages:
Or choose individual packages:
```sh
stow -d kde -t "$HOME" spectacle fastfetch
stow -d kde -t "$HOME" spectacle helium
```
| Package | Details |
| ---------------------------------- | -------------------------------------------------------- |
| [theme](theme/README.md) | Darwin Breeze KDE colors and generated app themes |
| [helium](helium/README.md) | Browser flags and the generated Helium theme |
| [fastfetch](fastfetch/README.md) | Konsole configuration and a pre-scaled inline image logo |
| [helium](helium/README.md) | Browser flags; colors come from GTK |
| [spectacle](spectacle/README.md) | Screenshots to clipboard with a notification, no preview |
| [tray-icons](tray-icons/README.md) | Ngó Sen and qBittorrent tray icons |
| [tray-icons](tray-icons/README.md) | Ngó Sen, NetBird and qBittorrent tray icons |
Theme and Spectacle need an additional apply step after stowing their package:
@@ -29,9 +28,8 @@ Theme and Spectacle need an additional apply step after stowing their package:
./kde/spectacle/apply.py
```
The apply scripts require a running KDE Plasma 6 session. Ngó Sen icons need
an additional install step described in the tray-icons README.
The apply scripts require a running KDE Plasma 6 session. Ngó Sen and NetBird
icons need an additional install step described in the tray-icons README.
Packages can be selected independently. Helium includes its generated theme;
it does not require the KDE theme to be installed. Choose either
`kde/fastfetch` or `mac/fastfetch`; both own `~/.config/fastfetch/`.
Packages can be selected independently; Helium follows the Darwin Breeze
colors only when the theme is applied.
@@ -1 +0,0 @@
_Ga=T,f=100,t=d,c=20,r=10,q=2,m=1;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\_Gm=1;2CzUiM53eKtBKKIrpLeyTzEjo54ifjUgpOfqaqxO8jgVGiX/zaZiv5kqvU+SQXJkYerUqQJ8bk6hBL52goHe22igSfbziCfL1U+A8b9pkDCguZ5MtWrGcHcPISB6wdTUCXr61qhS1Rz2toGIDGqAGlFZwkXD+4cTY/IXjiLOeTM831+7dm21Fl2BxbgZUkSoUB/8bzWV3tdEXvGvMd+K5SvE5Lg6hgi3xttErq7O+mFsWCNYmDoTAzUv4DbR7n+PHaXP107qKAyh0qXLw9HBCdUTYpDZpB6aZ9ZH3TopiAyPgJuzn9g34ukcj7jwTGGps69QKvHRVBgnHPbj+xw+fPhrIFQtyh7yeBUvBOH/salErz6t8qvyAL9UxBC348dPoHyFcrCx8JCYjybrP2Eq1v9sLN0RHZqB9MR2mjow7MvjRyk5obWUavXe39mCgN0E5sZuSEtNxoK543Ht4l48fXCeqJtu5/lVvHx6GfdvncTp45sxb/ZvaNemOXy9Auh+fIk5U4mdvxTxaV5UzM5erlEoUvRTsSdZ1v00RCg/chkuJW5cWBD9/9KQ73IZqV7liv7z+PETeHl7okpFY3lL5N93JitAqZvSCa4OoQQWD3g4xcLO0o9A4AVbKy/xaGflD3vrELKY676zFIfCqjGhjeDjGYgFc8YJsL14fAkP75zBnZzjuHXzGPWjot/JPYFHd8/i1VMC5YvruHFlP2ZMHY60Wsn0+x5wc4ghADanhdBKLA5jA3tYWVvi9u3bUI+HDELFKf+TAsBCnfA/aMhPnXeHnL2srHJl1Xfu0lmUPpOUeEXk/T3gsd4V7FubAOdPYtATdWun4MeBvTBn1hisXTUTmzfLine truncated
-26
View File
@@ -1,26 +0,0 @@
# Fastfetch for Konsole
From the repository root:
```sh
stow -d kde -t "$HOME" fastfetch
```
The configuration, source PNG, and generated logo live in
`kde/fastfetch/.config/fastfetch/`. The generated logo is included, so a new
machine does not need to run the build script.
Choose either this package or `mac/fastfetch`; both own `~/.config/fastfetch/`.
Konsole needs inline image data instead of the original `kitty-direct` file
transfer. The committed `albedo-konsole.kitty` logo is pre-scaled to 20 × 10
cells to avoid grainy terminal scaling.
After changing `albedo.png`, the logo dimensions, or Konsole's font, update
the cell size in `build.py` and regenerate the logo from the repository root:
```sh
python3 kde/fastfetch/build.py
```
The build script requires `vipsthumbnail` from libvips.
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env python3
"""Encode albedo.png as kitty graphics escapes for fastfetch's "raw" logo.
fastfetch's "kitty-direct" logo sends the terminal a file path (t=f). Konsole
only accepts inline data (t=d) and answers t=f with ENOTSUPPORTED, which lands
on the prompt as typed input. These files carry the PNG inline instead, with
q=2 so the terminal never answers at all.
.config/fastfetch/albedo-konsole.kitty pre-scaled for Konsole
Konsole stretches the image to the c x r cell box with nearest-neighbour
scaling, at logical (not device) pixel size, so a 512px source turns grainy.
The Konsole copy is resized with Lanczos to exactly that box, which leaves
Konsole nothing to scale. Rerun after changing the image, the logo size or the
Konsole font.
"""
import base64
import subprocess
import tempfile
from pathlib import Path
PKG = Path(__file__).resolve().parent
DIR = PKG / ".config/fastfetch"
SOURCE = DIR / "albedo.png"
# Logo size in cells; keep in sync with "width" and "height" in config.jsonc.
COLS, ROWS = 20, 10
# Konsole's cell size in logical pixels for JetBrainsMono Nerd Font 10pt
# (QFontMetrics: average advance of Konsole's REPCHAR, and height()).
KONSOLE_CELL = 8, 17
def encode(png: bytes, dst: Path) -> None:
data = base64.b64encode(png)
chunks = [data[i : i + 4096] for i in range(0, len(data), 4096)]
out = bytearray()
for n, chunk in enumerate(chunks):
more = int(n < len(chunks) - 1)
if n == 0:
keys = b"a=T,f=100,t=d,c=%d,r=%d,q=2,m=%d" % (COLS, ROWS, more)
else:
keys = b"m=%d" % more
out += b"\x1b_G" + keys + b";" + chunk + b"\x1b\\"
dst.write_bytes(out)
def resize(width: int, height: int) -> bytes:
with tempfile.TemporaryDirectory() as tmp:
out = Path(tmp) / "out.png"
subprocess.run(
["vipsthumbnail", SOURCE, "--size", f"{width}x{height}!", "-o", out],
check=True,
)
return out.read_bytes()
encode(resize(COLS * KONSOLE_CELL[0], ROWS * KONSOLE_CELL[1]), DIR / "albedo-konsole.kitty")
+4 -2
View File
@@ -2,5 +2,7 @@
--ozone-platform-hint=auto
--enable-features=AcceleratedVideoDecodeLinuxGL,AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoEncoder
# Darwin Breeze theme, generated by kde/theme/build.py
--load-extension=~/.local/share/helium-themes/darwinbreeze
# Don't tag surfaces as sRGB. KWin shows tagged sRGB through a gamma 2.2
# curve, which lifts dark grays (40 -> 44), so the tab strip would come out
# lighter than KDE title bars.
--disable-features=WaylandWpColorManagerV1
-2
View File
@@ -1,2 +0,0 @@
# Helium caches the built theme next to the manifest.
Cached Theme.pak
@@ -1,100 +0,0 @@
{
"manifest_version": 3,
"name": "Darwin Breeze",
"version": "1.0",
"description": "Generated by kde/theme/build.py from DarwinBreezeDark.colors",
"theme": {
"colors": {
"frame": [
35,
35,
35
],
"frame_inactive": [
35,
35,
35
],
"frame_incognito": [
35,
35,
35
],
"frame_incognito_inactive": [
35,
35,
35
],
"toolbar": [
35,
35,
35
],
"background_tab": [
60,
60,
60
],
"background_tab_inactive": [
60,
60,
60
],
"tab_text": [
223,
223,
223
],
"tab_background_text": [
223,
223,
223
],
"tab_background_text_inactive": [
155,
155,
155
],
"toolbar_text": [
223,
223,
223
],
"toolbar_button_icon": [
223,
223,
223
],
"bookmark_text": [
223,
223,
223
],
"omnibox_background": [
24,
24,
24
],
"omnibox_text": [
222,
222,
222
],
"ntp_background": [
24,
24,
24
],
"ntp_text": [
222,
222,
222
],
"ntp_link": [
63,
157,
255
]
}
}
}
+7 -4
View File
@@ -1,11 +1,14 @@
# Helium
Browser flags and the generated Darwin Breeze theme. From the repository root:
Browser flags for hardware video decoding on Wayland and for colors that
match KDE apps. From the repository root:
```sh
stow -d kde -t "$HOME" helium
```
The theme files are included, so the KDE `theme` package does not need to be
stowed to use Helium's theme. To regenerate its colors from the shared color
scheme, run `python3 kde/theme/build.py` and restart Helium.
Helium takes its colors and window buttons from GTK. In Settings →
Appearance, set Theme to GTK and turn off "Use system title bar and borders".
With the KDE `theme` package applied, GTK follows the Darwin Breeze colors and
the window buttons match KWin's; see
[Darwin Breeze → Helium notes](../theme/README.md#helium-notes).
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
GenericName=Screenshot Capture Utility
Name=Spectacle
+12
View File
@@ -0,0 +1,12 @@
/* Helium (Chromium) with its GTK theme. Chromium adds a "chromium" class to
every node of the GTK style contexts it reads colors from, so these rules
reach Helium and no other GTK app.
In the compact layout, the address bar takes the text field color and the
segment with the extension and window buttons takes the window color,
which Chromium resolves to the darker view color. Both get the title bar
color, so the strip reads as one bar like a KDE title bar. */
window.background.chromium,
textview.view.chromium {
background-color: @theme_bg_color_breeze;
}
+28 -18
View File
@@ -23,11 +23,11 @@ stow -d kde -t "$HOME" theme
```
`apply.sh` regenerates the app themes, applies the global theme, hides the
line under the title bar, turns off the wallpaper accent color and publishes
the T3 Code theme and installs the VS Code extension. Restart Helium
afterwards; T3 Code switches on its own. In VS Code, pick "Darwin Breeze Dark"
once under Preferences → Color Theme; after later color changes, run Developer:
Reload Window.
line under the title bar, turns off the wallpaper accent color, gives Helium
the KWin title buttons and an even tab strip, publishes the T3 Code theme and
installs the VS Code extension. Restart Helium afterwards; T3 Code switches on its own. In VS
Code, pick "Darwin Breeze Dark" once under Preferences → Color Theme; after
later color changes, run Developer: Reload Window.
Telegram has to import its theme by hand: Settings → Chat Settings → Chat
background → Choose from file, then pick
@@ -49,7 +49,7 @@ Edit it, then run `./kde/theme/apply.sh` again. Everything else follows:
| Qt / KDE apps | the color scheme itself |
| GTK apps | Breeze GTK, which `kde-gtk-config` syncs from the scheme |
| Tray icons in `kde/tray-icons/` | `ColorScheme-Text`, recolored by Plasma |
| Helium | `../helium/.local/share/helium-themes/`, generated by `build.py` |
| Helium | Breeze GTK, through Helium's GTK theme (see [Helium notes](#helium-notes)) |
| T3 Code | `t3/darwin-breeze.json`, generated by `build.py` and published with `t3 theme set` |
| VS Code | `vscode/package.json` and `vscode/themes/`, generated by `build.py`, packed into `darwin-breeze.vsix` and installed by `apply.sh` |
| Telegram | `telegram/DarwinBreezeDark.tdesktop-theme`, built by `build.py` and imported by hand |
@@ -81,7 +81,7 @@ are built by `build.py` instead. Don't edit generated files by hand.
are, without gamma compensation, so code looks the same in both apps.
- The terminal palette is generated from the color scheme.
- VS Code is Electron, so its workbench colors get the same gamma
compensation as Helium and T3 Code.
compensation as T3 Code.
- The `.vsix` isn't committed; `build.py` packs it from `vscode/`.
## Telegram notes
@@ -94,24 +94,34 @@ are built by `build.py` instead. Don't edit generated files by hand.
colors, are kept as night has them.
- The chat list, top bar and compose bar use the window color; the chat
behind the bubbles and the folder strip use the darker view color.
- Telegram is a Qt app, so unlike Helium and T3 Code it needs no gamma
- Telegram is a Qt app, so unlike T3 Code and VS Code it needs no gamma
compensation.
- The `.tdesktop-theme` is a zip, so it isn't committed; `build.py` builds it
from the night palette and the color scheme.
## Helium notes
- Helium loads the theme from `~/.local/share/helium-themes/darwinbreeze`
through `--load-extension` in `kde/helium/.config/helium-browser-flags.conf`.
Install it separately with `stow -d kde -t "$HOME" helium`.
- In Helium's compact layout the active tab and the toolbar segments on either
side share one theme color, so the toolbar is the same color as the tab
strip and inactive tabs are drawn lighter, as Dolphin does.
- Helium uses the GTK theme (Settings → Appearance → Theme: GTK) with "Use
system title bar and borders" off, so the tab strip doubles as the title
bar. Its colors come from Breeze GTK, which `kde-gtk-config` syncs from
the scheme; there is no Helium theme to generate.
- With the system title bar off, Helium draws the window buttons through
GTK. `kde-gtk-config` renders the KWin buttons into
`~/.config/gtk-3.0/window_decorations.css`, but hands that file to GTK apps
through a GTK module that Helium doesn't load, so Helium would show Breeze
GTK's own round buttons instead. `apply.sh` imports the file from `gtk.css`
so Helium picks it up too.
- In the compact layout, the address bar takes GTK's text field color and the
segment with the extension and window buttons takes the window color, which
Chromium resolves to the darker view color. `.config/gtk-3.0/chromium.css`
gives both the title bar color, and `apply.sh` imports it from `gtk.css`.
Chromium adds a `chromium` class to every GTK node it reads colors from, so
the rules reach Helium and no other GTK app.
- Chromium tags its output as sRGB and KWin shows it with a gamma 2.2 curve,
which lifts dark grays (40 becomes 44). `build.py` writes pre-compensated
values for Helium and T3 Code (both Chromium), so the theme files say 35
where the screen shows 40.
- Under Settings → Appearance the theme must be Classic, not GTK or QT.
which lifts dark grays (40 becomes 44). T3 Code and VS Code get
pre-compensated colors from `build.py`; Helium instead runs with
`--disable-features=WaylandWpColorManagerV1`, so its surfaces go untagged
and show the GTK colors as they are.
## When the colors don't show up
+16 -1
View File
@@ -21,6 +21,21 @@ plasma-apply-lookandfeel --apply dev.trivk.darwinbreeze.dark
plasma-apply-colorscheme BreezeClassic >/dev/null
plasma-apply-colorscheme DarwinBreezeDark >/dev/null
# kde-gtk-config writes gtk.css; add our imports to it.
gtk_import() {
local css="${XDG_CONFIG_HOME:-$HOME/.config}/$1/gtk.css"
if [ -e "$css" ] && ! grep -qF "'$2'" "$css"; then
sed -i "\$a @import '$2';" "$css"
fi
}
# kde-gtk-config gives GTK apps the KWin title buttons through a GTK module,
# which Helium doesn't load, so it would fall back to Breeze GTK's own
# buttons. Importing the same CSS from gtk.css reaches Helium too.
gtk_import gtk-3.0 window_decorations.css
gtk_import gtk-4.0 window_decorations.css
# Helium's tab strip colors (see chromium.css).
gtk_import gtk-3.0 chromium.css
# Drop the line under the title bar so it blends into the toolbar.
kwriteconfig6 --file breezerc --group Windeco --key DrawTitleBarSeparator false
@@ -45,5 +60,5 @@ fi
echo "Applied Darwin Breeze"
if pgrep -x helium >/dev/null; then
echo "Restart Helium to pick up its theme"
echo "Restart Helium to pick up the new colors"
fi
+3 -53
View File
@@ -4,7 +4,6 @@
.local/share/color-schemes/DarwinBreezeDark.colors is the only place colors
are defined. This script derives from it:
../helium/.local/share/helium-themes/darwinbreeze/manifest.json
t3/darwin-breeze.json (published to T3 Code by apply.sh)
telegram/DarwinBreezeDark.tdesktop-theme (not committed; imported into Telegram by hand)
vscode/ (packed into vscode/darwin-breeze.vsix, installed by apply.sh)
@@ -24,7 +23,6 @@ from pathlib import Path
HERE = Path(__file__).resolve().parent
PKG = HERE
SCHEME = PKG / ".local/share/color-schemes/DarwinBreezeDark.colors"
HELIUM = PKG.parent / "helium/.local/share/helium-themes/darwinbreeze"
T3 = HERE / "t3/darwin-breeze.json"
# Telegram Desktop's built-in night theme, used as the base for every key the
# scheme has no role for: the colors file from telegramdesktop/tdesktop at
@@ -54,10 +52,6 @@ CYAN = (100, 210, 255)
# Apple systemYellow; search highlights in T3 Code.
YELLOW = (255, 255, 0)
# Breeze draws inactive tabs this far from the window color towards the text
# color (measured in Dolphin: 40 -> 62 with text 222).
INACTIVE_TAB_MIX = 0.12
def load() -> ColorFn:
cp = configparser.ConfigParser(interpolation=None, strict=False)
@@ -84,7 +78,8 @@ def kwin_compensate(c: Sequence[int]) -> list[int]:
Chromium tags its surfaces as sRGB, KWin shows them with a gamma 2.2
curve, so dark grays come out lighter (40 -> 44). Encoding the target
with the inverse makes Helium land on the same values as native apps.
with the inverse makes Chromium apps land on the same values as native
apps.
"""
def ch(v: int) -> int:
@@ -95,40 +90,6 @@ def kwin_compensate(c: Sequence[int]) -> list[int]:
return [ch(v) for v in c]
def helium(color: ColorFn) -> dict[str, list[int]]:
bar = color("Header", "BackgroundNormal")
text = color("Window", "ForegroundNormal")
view = color("View", "BackgroundNormal")
view_text = color("View", "ForegroundNormal")
inactive_tab = mix(bar, text, INACTIVE_TAB_MIX)
# In Helium's compact layout the active tab and the toolbar segments on
# either side share the toolbar color, so toolbar and frame must match
# for the strip to read as one bar. Inactive tabs get the lighter Breeze
# tab color, the way Dolphin draws them.
colors = {
"frame": bar,
"frame_inactive": bar,
"frame_incognito": bar,
"frame_incognito_inactive": bar,
"toolbar": bar,
"background_tab": inactive_tab,
"background_tab_inactive": inactive_tab,
"tab_text": text,
"tab_background_text": text,
"tab_background_text_inactive": color("Window", "ForegroundInactive"),
"toolbar_text": text,
"toolbar_button_icon": text,
"bookmark_text": text,
"omnibox_background": view,
"omnibox_text": view_text,
"ntp_background": view,
"ntp_text": view_text,
"ntp_link": color("View", "ForegroundLink"),
}
return {k: kwin_compensate(v) for k, v in colors.items()}
def ansi(color: ColorFn) -> list[Color]:
"""The 16 colors for the VS Code terminal."""
bg = color("View", "BackgroundNormal")
@@ -530,7 +491,7 @@ def vscode(color: ColorFn) -> dict[str, str]:
colors = {k: pins.get(k) or recolor(c) for k, c in base.items()}
colors.update(pins)
# Electron is Chromium, so it needs the same KWin compensation as Helium.
# Electron is Chromium, so it needs the KWin compensation.
return {k: hexc(kwin_compensate(c[:3]) + list(c[3:])) for k, c in sorted(colors.items())}
@@ -638,17 +599,6 @@ def write(path: Path, data: str | bytes) -> bool:
def main() -> None:
color = load()
manifest = {
"manifest_version": 3,
"name": "Darwin Breeze",
"version": "1.0",
"description": f"Generated by kde/theme/build.py from {SCHEME.name}",
"theme": {"colors": helium(color)},
}
if write(HELIUM / "manifest.json", json.dumps(manifest, indent=2) + "\n"):
# Helium would keep showing the old colors from its cache.
(HELIUM / "Cached Theme.pak").unlink(missing_ok=True)
theme = {"version": 1, "name": "Darwin Breeze", "appearance": "dark", "colors": t3(color)}
write(T3, json.dumps(theme, indent=2) + "\n")
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
# Patch netbird-ui's tray icons to Breeze proportions.
# Runs as root; the pacman hook re-runs it after every netbird-ui install/upgrade.
set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
ROOT=${ROOT:-} # for dry-run testing only
bin=$ROOT/usr/bin/netbird-ui
[[ -f $bin ]] || exit 0
# Still the file the package installed: keep it as the original for restore.sh
sum=$(zcat "$ROOT"/var/lib/pacman/local/netbird-ui*/mtree 2>/dev/null \
| grep -F './usr/bin/netbird-ui ' | grep -o 'sha256digest=[0-9a-f]*' | cut -d= -f2 || true)
if [[ -n $sum && $(sha256sum <"$bin" | cut -d' ' -f1) == "$sum" ]]; then
install -Dm755 "$bin" "$here/orig/netbird-ui"
fi
python3 -I "$here/patch.py" "$bin"
@@ -0,0 +1,15 @@
#!/bin/bash
# Install the netbird-ui tray icon patch and its pacman hook. Usage: sudo ./install.sh
set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
ROOT=${ROOT:-} # for dry-run testing only
[[ $EUID -eq 0 || -n $ROOT ]] || { echo "Must be run with sudo"; exit 1; }
dest=$ROOT/usr/local/share/netbird-breeze-icons
install -Dm755 "$here/patch.py" "$dest/patch.py"
install -m755 "$here/apply.sh" "$dest/apply.sh"
install -Dm644 "$here/netbird-breeze-icons.hook" "$ROOT/etc/pacman.d/hooks/netbird-breeze-icons.hook"
ROOT=$ROOT "$dest/apply.sh"
echo "Done. Restart the NetBird UI (without sudo):"
echo " pkill -x netbird-ui; setsid -f netbird-ui --daemon-addr unix:///var/run/netbird/main.sock >/dev/null 2>&1"
@@ -0,0 +1,11 @@
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = netbird-ui
Target = netbird-ui-bin
[Action]
Description = Patching NetBird tray icons to match Breeze...
When = PostTransaction
Exec = /usr/local/share/netbird-breeze-icons/apply.sh
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""Shrink and recolor the tray icons embedded in the netbird-ui binary.
NetBird sends its tray icon as a pixmap, so an icon theme can't replace it.
This rewrites the 48x48 monochrome PNGs inside the binary in place: the
glyph is scaled to SCALE so it gets the padding of a Breeze status icon,
and the white variants take the panel's text color. Each new PNG must fit
in the bytes of the old one; the rest is zero-filled, which Go's PNG
decoder never reads because it stops at IEND.
Usage: patch.py BINARY [--check]
"""
import argparse
import os
from pathlib import Path
import struct
import sys
import tempfile
import zlib
SIZE = 48
SCALE = 0.8
# DarwinBreezeDark ForegroundNormal, as the other tray icons are drawn
LIGHT = (222, 222, 222)
# A glyph narrower than this already has padding (already patched)
MAX_PADDED_WIDTH = round(SIZE * 0.9)
SIG = b"\x89PNG\r\n\x1a\n"
Pixels = list[list[float]] # rows of premultiplied RGBA floats
def decode(png: bytes) -> tuple[int, int, bytes] | None:
"""Return (width, height, RGBA rows) for an 8-bit RGBA PNG, else None."""
pos, idat, ihdr = 8, b"", b""
while pos < len(png):
(length,) = struct.unpack(">I", png[pos:pos + 4])
kind, body = png[pos + 4:pos + 8], png[pos + 8:pos + 8 + length]
if kind == b"IHDR":
ihdr = body
elif kind == b"IDAT":
idat += body
elif kind == b"IEND":
break
pos += 12 + length
w, h, depth, color, _, _, interlace = struct.unpack(">IIBBBBB", ihdr)
if (depth, color, interlace) != (8, 6, 0):
return None
raw, stride, out = zlib.decompress(idat), w * 4, bytearray()
prev = bytearray(stride)
for y in range(h):
ftype = raw[y * (stride + 1)]
line = bytearray(raw[y * (stride + 1) + 1:(y + 1) * (stride + 1)])
for i in range(stride):
a = line[i - 4] if i >= 4 else 0
b = prev[i]
c = prev[i - 4] if i >= 4 else 0
if ftype == 1:
line[i] = (line[i] + a) & 0xFF
elif ftype == 2:
line[i] = (line[i] + b) & 0xFF
elif ftype == 3:
line[i] = (line[i] + (a + b) // 2) & 0xFF
elif ftype == 4:
p = a + b - c
pa, pb, pc = abs(p - a), abs(p - b), abs(p - c)
pred = a if pa <= pb and pa <= pc else b if pb <= pc else c
line[i] = (line[i] + pred) & 0xFF
out += line
prev = line
return w, h, bytes(out)
def encode(w: int, h: int, rgba: bytes) -> bytes:
def chunk(kind: bytes, body: bytes) -> bytes:
crc = zlib.crc32(kind + body)
return struct.pack(">I", len(body)) + kind + body + struct.pack(">I", crc)
raw = b"".join(b"\0" + rgba[y * w * 4:(y + 1) * w * 4] for y in range(h))
return (SIG + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
def glyph_width(rgba: bytes) -> int:
xs = [i // 4 % SIZE for i in range(3, len(rgba), 4) if rgba[i] > 16]
return max(xs) - min(xs) + 1 if xs else 0
def is_light(rgba: bytes) -> bool | None:
"""True for a white glyph, False for black, None if not monochrome."""
lum = [rgba[i - 3] for i in range(3, len(rgba), 4) if rgba[i] > 200]
if not lum or any(abs(rgba[i - 3] - rgba[i - 2]) > 8 for i in range(3, len(rgba), 4)
if rgba[i] > 200):
return None
if all(v > 230 for v in lum):
return True
if all(v < 25 for v in lum):
return False
return None
def shrink(rgba: bytes, light: bool) -> bytes:
"""Box-filter the image to SCALE, centered on a SIZE canvas."""
src: Pixels = []
for y in range(SIZE):
row: list[float] = []
for x in range(SIZE):
r, g, b, a = rgba[(y * SIZE + x) * 4:(y * SIZE + x) * 4 + 4]
row += [r * a, g * a, b * a, a]
src.append(row)
inner = SIZE * SCALE
off = (SIZE - inner) / 2
step = 1 / SCALE
out = bytearray(SIZE * SIZE * 4)
def spans(o: int) -> list[tuple[int, float]]:
lo = (o - off) * step
hi = lo + step
res = []
for s in range(max(0, int(lo)), min(SIZE, int(hi) + 1)):
cover = min(hi, s + 1) - max(lo, s)
if cover > 0:
res.append((s, cover))
return res
for oy in range(SIZE):
ys = spans(oy)
for ox in range(SIZE):
acc = [0.0] * 4
for sy, wy in ys:
for sx, wx in spans(ox):
k = wy * wx
for c in range(4):
acc[c] += src[sy][sx * 4 + c] * k
area = step * step
a = acc[3] / area
if a < 0.5:
continue
color = LIGHT if light else tuple(round(acc[c] / acc[3]) for c in range(3))
out[(oy * SIZE + ox) * 4:(oy * SIZE + ox) * 4 + 4] = bytes(
[*color, min(255, round(a))])
return bytes(out)
def patch(data: bytearray) -> int:
count, pos = 0, 0
while (pos := data.find(SIG, pos)) >= 0:
end = data.find(b"IEND", pos) + 8
old = bytes(data[pos:end])
img = decode(old) if old[16:24] == struct.pack(">II", SIZE, SIZE) else None
if img is not None:
light = is_light(img[2])
if light is not None and glyph_width(img[2]) > MAX_PADDED_WIDTH:
new = encode(SIZE, SIZE, shrink(img[2], light))
if len(new) > len(old):
sys.exit(f"Patched icon at {pos} is larger than the original")
data[pos:end] = new + bytes(len(old) - len(new))
count += 1
pos = end
return count
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("binary", type=Path)
parser.add_argument("--check", action="store_true",
help="only print how many icons would be patched")
args = parser.parse_args()
data = bytearray(args.binary.read_bytes())
count = patch(data)
print(f"{args.binary}: {count} tray icons {'to patch' if args.check else 'patched'}")
if args.check or not count:
return
mode = args.binary.stat().st_mode
fd, tmp = tempfile.mkstemp(dir=args.binary.parent, prefix=".netbird-ui.")
with os.fdopen(fd, "wb") as f:
f.write(data)
os.chmod(tmp, mode & 0o7777)
os.replace(tmp, args.binary)
if __name__ == "__main__":
main()
@@ -0,0 +1,12 @@
#!/bin/bash
# Put the original netbird-ui back and remove the hook. Usage: sudo ./restore.sh
set -euo pipefail
ROOT=${ROOT:-} # for dry-run testing only
[[ $EUID -eq 0 || -n $ROOT ]] || { echo "Must be run with sudo"; exit 1; }
dest=$ROOT/usr/local/share/netbird-breeze-icons
[[ -f $dest/orig/netbird-ui ]] || { echo "No original binary in $dest/orig"; exit 1; }
install -m755 "$dest/orig/netbird-ui" "$ROOT/usr/bin/netbird-ui"
rm -f "$ROOT/etc/pacman.d/hooks/netbird-breeze-icons.hook"
rm -rf "$dest"
echo "Restored. Restart the NetBird UI (without sudo):"
echo " pkill -x netbird-ui; setsid -f netbird-ui --daemon-addr unix:///var/run/netbird/main.sock >/dev/null 2>&1"
+23
View File
@@ -31,6 +31,29 @@ systemctl --user restart plasma-plasmashell
- On KDE Wayland don't start fcitx5 with `fcitx5 -d`; use
`restart-fcitx5.sh` so KWin launches it.
## NetBird (netbird-ui)
Scales NetBird's tray icons down to 80% so they get the padding of Breeze
status icons, and draws the white ones in the panel's text color
(`#dedede`) instead of pure white.
```sh
sudo ~/.local/share/netbird-breeze-icons/install.sh
pkill -x netbird-ui; setsid -f netbird-ui --daemon-addr unix:///var/run/netbird/main.sock >/dev/null 2>&1
```
- NetBird sends the tray icon as raw pixels, not an icon name, so an icon
theme can't replace it. `patch.py` rewrites the 48x48 PNGs embedded in
`/usr/bin/netbird-ui` in place instead; each new PNG fits in the bytes
of the old one.
- `install.sh` copies `patch.py` and `apply.sh` to
`/usr/local/share/netbird-breeze-icons/` and installs the pacman hook
`/etc/pacman.d/hooks/netbird-breeze-icons.hook` to re-patch the binary
after every `netbird-ui`/`netbird-ui-bin` upgrade.
- The original binary is kept in `/usr/local/share/netbird-breeze-icons/orig/`;
`sudo ~/.local/share/netbird-breeze-icons/restore.sh` puts it back and
removes the hook.
## qBittorrent
`qbittorrent-tray-mono.svg` is qBittorrent's monochrome tray icon scaled
+1 -7
View File
@@ -6,16 +6,10 @@ From the repository root, install all macOS packages:
(cd mac && stow -t "$HOME" */)
```
Or choose individual packages:
Install the package:
```sh
stow -d mac -t "$HOME" ghostty
stow -d mac -t "$HOME" fastfetch
```
- `ghostty`: JetBrainsMono Nerd Font and Ghostty's bundled SpaceGray Eighties theme.
- `fastfetch`: the original configuration, with a `kitty-direct` image logo.
Its source PNG is included in `fastfetch/.config/fastfetch/`.
These packages are independent. Choose either `mac/fastfetch` or
`kde/fastfetch`; both own `~/.config/fastfetch/`.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

@@ -1,80 +0,0 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/master/doc/json_schema.json",
"display": {
"key": {
"width": 10
},
"size": {
"binaryPrefix": "jedec"
},
"separator": ""
},
"logo": {
"type": "kitty-direct",
"source": "~/.config/fastfetch/albedo.png",
"width": 20,
"height": 10
},
"modules": [
"break",
{
"type": "os",
"key": "os",
"keyColor": "yellow",
"format": "{name}"
},
{
"type": "kernel",
"key": "ker",
"keyColor": "green"
},
{
"type": "packages",
"key": "pkgs",
"keyColor": "cyan"
},
{
"type": "shell",
"key": "sh",
"keyColor": "blue",
"format": "{pretty-name}"
},
{
"type": "wm",
"key": "wm",
"keyColor": "red",
"format": "{pretty-name}"
},
{
"type": "uptime",
"key": "up",
"keyColor": "green"
},
{
"type": "cpu",
"key": "cpu",
"keyColor": "red",
"format": "{name}"
},
{
"type": "memory",
"key": "ram",
"keyColor": "yellow",
"format": "{used} / {total}"
},
{
"type": "disk",
"key": "disk",
"keyColor": "cyan",
"folders": [
"/"
],
"format": "{size-used} / {size-total}"
},
"break",
{
"type": "custom",
"format": "\u001b[33m󰮯 \u001b[32m󰊠 \u001b[34m󰊠 \u001b[31m󰊠 \u001b[36m󰊠 \u001b[35m󰊠 \u001b[37m󰊠 \u001b[97m󰊠"
}
]
}