mirror of
https://github.com/caelestia-dots/cli.git
synced 2026-06-05 14:59:29 -05:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f1d008cce | |||
| 66bce26841 | |||
| e1531f3c9e | |||
| 7f59ca9656 | |||
| 4cf9e8603f | |||
| b4b26ab5d5 | |||
| b4758901f9 | |||
| 60284ca41e | |||
| a019c42244 | |||
| 950c40bbd4 | |||
| d054129db8 | |||
| e497ca87eb | |||
| e81b1b87f0 | |||
| 1b823554ad | |||
| 4538e9cb50 | |||
| e5c161d43a | |||
| 11bdbc9e80 | |||
| 5bb3276b14 | |||
| 3930ca09b9 | |||
| 305f02d8aa | |||
| 8c7eea556d | |||
| 6bb09e7703 | |||
| b023936e1d | |||
| 3cdc2ce464 | |||
| 534d11d66a | |||
| 0d8be30138 | |||
| 8c6d933267 | |||
| b00c601d0a | |||
| c930bd2604 | |||
| cc155cf432 | |||
| 6e59149fbf | |||
| 8d2b737f15 | |||
| 4bcd42f482 | |||
| 51cecd481c | |||
| c9312f3928 | |||
| bfaf4fc373 | |||
| 6e711ec289 | |||
| 7899f8348f | |||
| b0d68f0a1c | |||
| b0325a1898 | |||
| a6defd2921 | |||
| 0b9e416175 | |||
| 8ce97ea3f5 | |||
| 25c473c18e | |||
| c22916fe45 | |||
| 011989e3ca | |||
| d88cc7ff79 | |||
| a550eb79ed |
@@ -4,43 +4,17 @@ The main control script for the Caelestia dotfiles.
|
||||
|
||||
<details><summary id="dependencies">External dependencies</summary>
|
||||
|
||||
- [`libnotfy`](https://gitlab.gnome.org/GNOME/libnotify) - sending notifications
|
||||
- [`swappy`](https://github.com/jtheoof/swappy) - screenshot editor
|
||||
- [`grim`](https://gitlab.freedesktop.org/emersion/grim) - taking screenshots
|
||||
- [`dart-sass`](https://github.com/sass/dart-sass) - discord theming
|
||||
- [`app2unit`](https://github.com/Vladimir-csp/app2unit) - launching apps
|
||||
- [`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) - copying to clipboard
|
||||
- [`slurp`](https://github.com/emersion/slurp) - selecting an area
|
||||
- [`gpu-screen-recorder`](https://git.dec05eba.com/gpu-screen-recorder/about) - screen recording
|
||||
- `glib2` - closing notifications
|
||||
- [`cliphist`](https://github.com/sentriz/cliphist) - clipboard history
|
||||
- [`fuzzel`](https://codeberg.org/dnkl/fuzzel) - clipboard history/emoji picker
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary id="optional-dependencies">Optional dependencies</summary>
|
||||
|
||||
- [`papirus-folders`](https://github.com/PapirusDevelopmentTeam/papirus-folders) - automatic folder icon color syncing with theme
|
||||
|
||||
> [!NOTE]
|
||||
> For automatic Papirus folder icon color syncing, `papirus-folders` needs to be able to run with `sudo` without a password prompt.
|
||||
>
|
||||
> **Recommended** - Create a sudoers file:
|
||||
> ```fish
|
||||
> # Fish shell
|
||||
> echo "$USER ALL=(ALL) NOPASSWD: "(which papirus-folders) | sudo tee /etc/sudoers.d/papirus-folders
|
||||
> sudo chmod 440 /etc/sudoers.d/papirus-folders
|
||||
> ```
|
||||
> ```sh
|
||||
> # Bash/other shells
|
||||
> echo "$USER ALL=(ALL) NOPASSWD: $(which papirus-folders)" | sudo tee /etc/sudoers.d/papirus-folders
|
||||
> sudo chmod 440 /etc/sudoers.d/papirus-folders
|
||||
> ```
|
||||
>
|
||||
> **Alternatively** - Edit the main sudoers file by running `sudo visudo` and adding at the end:
|
||||
> ```
|
||||
> your_username ALL=(ALL) NOPASSWD: /usr/bin/papirus-folders
|
||||
> ```
|
||||
- [`libnotfy`](https://gitlab.gnome.org/GNOME/libnotify) - sending notifications
|
||||
- [`swappy`](https://github.com/jtheoof/swappy) - screenshot editor
|
||||
- [`grim`](https://gitlab.freedesktop.org/emersion/grim) - taking screenshots
|
||||
- [`dart-sass`](https://github.com/sass/dart-sass) - discord theming
|
||||
- [`app2unit`](https://github.com/Vladimir-csp/app2unit) - launching apps
|
||||
- [`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) - copying to clipboard
|
||||
- [`slurp`](https://github.com/emersion/slurp) - selecting an area
|
||||
- [`gpu-screen-recorder`](https://git.dec05eba.com/gpu-screen-recorder/about) - screen recording
|
||||
- `glib2` - closing notifications
|
||||
- [`cliphist`](https://github.com/sentriz/cliphist) - clipboard history
|
||||
- [`fuzzel`](https://codeberg.org/dnkl/fuzzel) - clipboard history/emoji picker
|
||||
|
||||
</details>
|
||||
|
||||
@@ -119,6 +93,45 @@ sudo python -m installer dist/*.whl
|
||||
sudo cp completions/caelestia.fish /usr/share/fish/vendor_completions.d/caelestia.fish
|
||||
```
|
||||
|
||||
### Additional steps
|
||||
|
||||
#### Auto folder colour theming
|
||||
|
||||
For automatic Papirus folder icon colour syncing, you must have [`papirus-folders`](https://github.com/PapirusDevelopmentTeam/papirus-folders)
|
||||
installed, and `papirus-folders` must to be able to run with `sudo` without a password prompt.
|
||||
|
||||
You can allow this by creating a sudoers file:
|
||||
|
||||
```sh
|
||||
echo "$USER ALL=(ALL) NOPASSWD: $(which papirus-folders)" | sudo tee /etc/sudoers.d/papirus-folders
|
||||
sudo chmod 440 /etc/sudoers.d/papirus-folders
|
||||
```
|
||||
|
||||
#### Chromium-based browser theming
|
||||
|
||||
For live Chromium-based browser theming, the CLI must be allowed to create certain directories in `/etc`
|
||||
and write to them via `sudo` without a password prompt.
|
||||
|
||||
You can allow this by creating a sudoers file:
|
||||
|
||||
```fish
|
||||
# Fish shell
|
||||
for dir in /etc/chromium/policies/managed /etc/brave/policies/managed /etc/opt/chrome/policies/managed
|
||||
echo "$USER ALL=(ALL) NOPASSWD: $(which mkdir) -p $dir" | sudo tee -a /etc/sudoers.d/caelestia-chromium
|
||||
echo "$USER ALL=(ALL) NOPASSWD: $(which tee) $dir/caelestia.json" | sudo tee -a /etc/sudoers.d/caelestia-chromium
|
||||
end
|
||||
sudo chmod 440 /etc/sudoers.d/caelestia-chromium
|
||||
```
|
||||
|
||||
```sh
|
||||
# Bash/other shells
|
||||
for dir in /etc/chromium/policies/managed /etc/brave/policies/managed /etc/opt/chrome/policies/managed; do
|
||||
echo "$USER ALL=(ALL) NOPASSWD: $(which mkdir) -p $dir" | sudo tee -a /etc/sudoers.d/caelestia-chromium
|
||||
echo "$USER ALL=(ALL) NOPASSWD: $(which tee) $dir/caelestia.json" | sudo tee -a /etc/sudoers.d/caelestia-chromium
|
||||
done
|
||||
sudo chmod 440 /etc/sudoers.d/caelestia-chromium
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
All subcommands/options can be explored via the help flag.
|
||||
@@ -148,6 +161,24 @@ subcommands:
|
||||
resizer window resizer daemon
|
||||
```
|
||||
|
||||
### User templates
|
||||
|
||||
Custom user templates can be defined in `~/.config/caelestia/templates/`.
|
||||
|
||||
#### Template syntax
|
||||
|
||||
`{{ <color>.<format> }}`
|
||||
|
||||
- `<color>` is a theme color role derived from the Material You color system (e.g. `primary`, `secondary`, `background`)
|
||||
- `<format>` is the output format: `hex` or `rgb`
|
||||
|
||||
#### Examples
|
||||
|
||||
- `{{ primary.hex }}` outputs `3f4ba2`
|
||||
- `{{ primary.rgb }}` outputs `rgb(193, 132, 207)`
|
||||
|
||||
Output files are written to `~/.local/state/caelestia/theme/`. You can symlink them to your desired locations.
|
||||
|
||||
## Configuring
|
||||
|
||||
All configuration options are in `~/.config/caelestia/cli.json`.
|
||||
@@ -160,7 +191,7 @@ All configuration options are in `~/.config/caelestia/cli.json`.
|
||||
"extraArgs": []
|
||||
},
|
||||
"wallpaper": {
|
||||
"postHook": "echo $WALLPAPER_PATH"
|
||||
"postHook": "echo $WALLPAPER_PATH"
|
||||
},
|
||||
"theme": {
|
||||
"enableTerm": true,
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ python3.pkgs.buildPythonApplication {
|
||||
--replace-fail 'app2unit' ${app2unit}/bin/app2unit
|
||||
|
||||
# Use config style instead of darkly
|
||||
substituteInPlace src/caelestia/data/templates/qtct.conf \
|
||||
substituteInPlace src/caelestia/data/templates/qtengine.json \
|
||||
--replace-fail 'Darkly' '${qtctStyle}'
|
||||
'';
|
||||
|
||||
|
||||
Generated
+10
-10
@@ -9,11 +9,11 @@
|
||||
"quickshell": "quickshell"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770949235,
|
||||
"narHash": "sha256-OFeud9FjaOk6xHp/9igYl/+Zw6FJDyZNrIDNi47gsG0=",
|
||||
"lastModified": 1775801889,
|
||||
"narHash": "sha256-q1LGwhQbNOurIAClh5YwKVU2kJ5lTCxRYZf48bAb9IM=",
|
||||
"owner": "caelestia-dots",
|
||||
"repo": "shell",
|
||||
"rev": "93e8880842b03e251bf59d1ba316f2393c68574f",
|
||||
"rev": "0e07176ff149d02391531c802b51c28e73185f30",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -24,11 +24,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1770841267,
|
||||
"narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=",
|
||||
"lastModified": 1775710090,
|
||||
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae",
|
||||
"rev": "4c1018dae018162ec878d42fec712642d214fdfa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -46,11 +46,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769593411,
|
||||
"narHash": "sha256-WW00FaBiUmQyxvSbefvgxIjwf/WmRrEGBbwMHvW/7uQ=",
|
||||
"lastModified": 1772925576,
|
||||
"narHash": "sha256-mMoiXABDtkSJxCYDrkhJ/TrrJf5M46oUfIlJvv2gkZ0=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "1e4d804e7f3fa7465811030e8da2bf10d544426a",
|
||||
"revCount": 732,
|
||||
"rev": "15a84097653593dd15fad59a56befc2b7bdc270d",
|
||||
"revCount": 750,
|
||||
"type": "git",
|
||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
background 0a0f0f
|
||||
onBackground dce8e6
|
||||
surface 0a0f0f
|
||||
surfaceDim 0a0f0f
|
||||
surfaceBright 242e2d
|
||||
surfaceContainerLowest 000000
|
||||
surfaceContainerLow 0e1514
|
||||
surfaceContainer 131b1a
|
||||
surfaceContainerHigh 192120
|
||||
surfaceContainerHighest 1d2827
|
||||
onSurface dce8e6
|
||||
surfaceVariant 1d2827
|
||||
onSurfaceVariant a2adac
|
||||
outline 6d7876
|
||||
outlineVariant 3f4a49
|
||||
inverseSurface f6faf9
|
||||
inverseOnSurface 515655
|
||||
shadow 000000
|
||||
scrim 000000
|
||||
surfaceTint 9bd0cc
|
||||
primary 9bd0cc
|
||||
primaryDim 8ec2bf
|
||||
onPrimary 0d4845
|
||||
primaryContainer 255b58
|
||||
onPrimaryContainer b8ede9
|
||||
inversePrimary 336764
|
||||
primaryFixed b7ede9
|
||||
primaryFixedDim a9deda
|
||||
onPrimaryFixed 0c4744
|
||||
onPrimaryFixedVariant 306461
|
||||
secondary b0ccc9
|
||||
secondaryDim a3bebc
|
||||
onSecondary 2c4543
|
||||
secondaryContainer 27403e
|
||||
onSecondaryContainer a9c5c2
|
||||
secondaryFixed cce8e5
|
||||
secondaryFixedDim bedad7
|
||||
onSecondaryFixed 2b4442
|
||||
onSecondaryFixedVariant 47605e
|
||||
tertiary d5efff
|
||||
tertiaryDim b6e3fe
|
||||
onTertiary 2e5c72
|
||||
tertiaryContainer b6e3fe
|
||||
onTertiaryContainer 255369
|
||||
tertiaryFixed b6e3fe
|
||||
tertiaryFixedDim a8d5ef
|
||||
onTertiaryFixed 0b4156
|
||||
onTertiaryFixedVariant 2f5d73
|
||||
error fa746f
|
||||
errorDim c54d4a
|
||||
onError 490006
|
||||
errorContainer 871f21
|
||||
onErrorContainer ff9993
|
||||
primaryPaletteKeyColor 4c807d
|
||||
secondaryPaletteKeyColor 627c7a
|
||||
tertiaryPaletteKeyColor 517d94
|
||||
neutralPaletteKeyColor 737877
|
||||
neutralVariantPaletteKeyColor 6e7978
|
||||
errorPaletteKeyColor c84f4c
|
||||
primary_paletteKeyColor 4c807d
|
||||
secondary_paletteKeyColor 627c7a
|
||||
tertiary_paletteKeyColor 517d94
|
||||
neutral_paletteKeyColor 737877
|
||||
neutral_variant_paletteKeyColor 6e7978
|
||||
term0 343434
|
||||
term1 769e00
|
||||
term2 56e2c0
|
||||
term3 81fcce
|
||||
term4 76b6b3
|
||||
term5 7aaee9
|
||||
term6 83d8c9
|
||||
term7 cddcd3
|
||||
term8 9aa59e
|
||||
term9 85b900
|
||||
term10 41f7d0
|
||||
term11 cdffe9
|
||||
term12 a3c8c3
|
||||
term13 a2c0f7
|
||||
term14 8bedd9
|
||||
term15 ffffff
|
||||
rosewater f1f3e5
|
||||
flamingo e3e4c5
|
||||
pink bae2ff
|
||||
mauve 60cfe8
|
||||
red 8ab5ff
|
||||
maroon abbef0
|
||||
peach a9daac
|
||||
yellow d3fae8
|
||||
green 8df1df
|
||||
teal 9feee7
|
||||
sky 93eae9
|
||||
sapphire 70d7db
|
||||
blue 57cdda
|
||||
lavender 86d9e7
|
||||
klink 00969e
|
||||
klinkSelection 00969e
|
||||
kvisited 008ca9
|
||||
kvisitedSelection 008ca9
|
||||
knegative 838f00
|
||||
knegativeSelection 838f00
|
||||
kneutral 34c359
|
||||
kneutralSelection 34c359
|
||||
kpositive 00beab
|
||||
kpositiveSelection 00beab
|
||||
text dce8e6
|
||||
subtext1 a2adac
|
||||
subtext0 6d7876
|
||||
overlay2 5f6967
|
||||
overlay1 505958
|
||||
overlay0 434b4a
|
||||
surface2 353d3c
|
||||
surface1 282e2e
|
||||
surface0 191f1e
|
||||
base 0a0f0f
|
||||
mantle 0a0f0f
|
||||
crust 090e0e
|
||||
success B5CCBA
|
||||
onSuccess 213528
|
||||
successContainer 374B3E
|
||||
onSuccessContainer D1E9D6
|
||||
@@ -0,0 +1,120 @@
|
||||
background f6faf9
|
||||
onBackground 2a3433
|
||||
surface f6faf9
|
||||
surfaceDim d1dcdb
|
||||
surfaceBright f6faf9
|
||||
surfaceContainerLowest ffffff
|
||||
surfaceContainerLow eef5f3
|
||||
surfaceContainer e7f0ee
|
||||
surfaceContainerHigh e1eae8
|
||||
surfaceContainerHighest d9e5e3
|
||||
onSurface 2a3433
|
||||
surfaceVariant d9e5e3
|
||||
onSurfaceVariant 566160
|
||||
outline 727d7c
|
||||
outlineVariant a9b4b3
|
||||
inverseSurface 0a0f0f
|
||||
inverseOnSurface 999e9d
|
||||
shadow 000000
|
||||
scrim 000000
|
||||
surfaceTint 1c6a66
|
||||
primary 1c6a66
|
||||
primaryDim 045d5a
|
||||
onPrimary e1fffc
|
||||
primaryContainer a8f0eb
|
||||
onPrimaryContainer 015c59
|
||||
inversePrimary b0f8f3
|
||||
primaryFixed a8f0eb
|
||||
primaryFixedDim 9ae1dc
|
||||
onPrimaryFixed 004845
|
||||
onPrimaryFixedVariant 166663
|
||||
secondary 4a6462
|
||||
secondaryDim 3e5856
|
||||
onSecondary e2fffc
|
||||
secondaryContainer cce8e5
|
||||
onSecondaryContainer 3d5654
|
||||
secondaryFixed cce8e5
|
||||
secondaryFixedDim bedad7
|
||||
onSecondaryFixed 2b4442
|
||||
onSecondaryFixedVariant 47605e
|
||||
tertiary 37647b
|
||||
tertiaryDim 2a586e
|
||||
onTertiary f4faff
|
||||
tertiaryContainer b6e3fe
|
||||
onTertiaryContainer 255369
|
||||
tertiaryFixed b6e3fe
|
||||
tertiaryFixedDim a8d5ef
|
||||
onTertiaryFixed 0b4156
|
||||
onTertiaryFixedVariant 2f5d73
|
||||
error a83836
|
||||
errorDim 67040d
|
||||
onError fff7f6
|
||||
errorContainer fa746f
|
||||
onErrorContainer 6e0a12
|
||||
primaryPaletteKeyColor 3a827e
|
||||
secondaryPaletteKeyColor 627c7a
|
||||
tertiaryPaletteKeyColor 517d94
|
||||
neutralPaletteKeyColor 737877
|
||||
neutralVariantPaletteKeyColor 6e7978
|
||||
errorPaletteKeyColor c84f4c
|
||||
primary_paletteKeyColor 3a827e
|
||||
secondary_paletteKeyColor 627c7a
|
||||
tertiary_paletteKeyColor 517d94
|
||||
neutral_paletteKeyColor 737877
|
||||
neutral_variant_paletteKeyColor 6e7978
|
||||
term0 9a9b99
|
||||
term1 005bcc
|
||||
term2 00907c
|
||||
term3 427d3b
|
||||
term4 269a7a
|
||||
term5 0071a3
|
||||
term6 128f8d
|
||||
term7 1f2324
|
||||
term8 0f0f0f
|
||||
term9 0071fa
|
||||
term10 00b49c
|
||||
term11 5d9954
|
||||
term12 52be9c
|
||||
term13 008cca
|
||||
term14 45b0ae
|
||||
term15 25292a
|
||||
rosewater 6b8647
|
||||
flamingo 6f7c1e
|
||||
pink 0085c0
|
||||
mauve 005d6c
|
||||
red 515900
|
||||
maroon 606c00
|
||||
peach 198900
|
||||
yellow 008f67
|
||||
green 007d6d
|
||||
teal 007573
|
||||
sky 00878d
|
||||
sapphire 008080
|
||||
blue 00636d
|
||||
lavender 007e8b
|
||||
klink 00969d
|
||||
klinkSelection 00969e
|
||||
kvisited 008ca9
|
||||
kvisitedSelection 008ca9
|
||||
knegative 838f00
|
||||
knegativeSelection 838f00
|
||||
kneutral 34c359
|
||||
kneutralSelection 34c359
|
||||
kpositive 00beab
|
||||
kpositiveSelection 00beac
|
||||
text 2a3433
|
||||
subtext1 566160
|
||||
subtext0 727d7c
|
||||
overlay2 828c8b
|
||||
overlay1 949d9c
|
||||
overlay0 a5aead
|
||||
surface2 b8bfbe
|
||||
surface1 cbd1d0
|
||||
surface0 e1e6e5
|
||||
base f6faf9
|
||||
mantle eef1f0
|
||||
crust e9eceb
|
||||
success 4F6354
|
||||
onSuccess FFFFFF
|
||||
successContainer D1E8D5
|
||||
onSuccessContainer 0C1F13
|
||||
@@ -0,0 +1,162 @@
|
||||
{
|
||||
"$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json",
|
||||
"name": "Caelestia",
|
||||
"author": "Unrectified",
|
||||
"url": "https://github.com/caelestia-dots/cli",
|
||||
"themes": [
|
||||
{
|
||||
"name": "Caelestia",
|
||||
"mode": "{{ $mode }}",
|
||||
"colors": {
|
||||
"accent.background": "{{ $surfaceContainerHigh }}",
|
||||
"accent.foreground": "{{ $onSurface }}",
|
||||
"background": "{{ $background }}",
|
||||
"border": "{{ $outlineVariant }}",
|
||||
"danger.background": "{{ $error }}",
|
||||
"foreground": "{{ $onBackground }}",
|
||||
"input.border": "{{ $outline }}",
|
||||
"link.active.foreground": "{{ $primary }}",
|
||||
"link.foreground": "{{ $primary }}",
|
||||
"link.hover.foreground": "{{ $primaryFixed }}",
|
||||
"list.active.background": "{{ $secondaryContainer }}",
|
||||
"list.active.border": "{{ $secondary }}",
|
||||
"list.even.background": "{{ $surfaceContainerLowest }}",
|
||||
"muted.background": "{{ $surfaceVariant }}",
|
||||
"muted.foreground": "{{ $onSurfaceVariant }}",
|
||||
"panel.background": "{{ $surfaceContainer }}",
|
||||
"popover.background": "{{ $surfaceContainerHigh }}",
|
||||
"popover.foreground": "{{ $onSurface }}",
|
||||
"primary.active.background": "{{ $primaryFixedDim }}",
|
||||
"primary.background": "{{ $primary }}",
|
||||
"primary.foreground": "{{ $onPrimary }}",
|
||||
"primary.hover.background": "{{ $primaryFixed }}",
|
||||
"scrollbar.background": "{{ $surface }}",
|
||||
"scrollbar.thumb.background": "{{ $outline }}",
|
||||
"secondary.background": "{{ $secondaryContainer }}",
|
||||
"secondary.active.background": "{{ $secondaryFixedDim }}",
|
||||
"secondary.foreground": "{{ $onSecondary }}",
|
||||
"secondary.hover.background": "{{ $secondaryFixed }}",
|
||||
"tab.active.background": "{{ $surface }}",
|
||||
"tab.active.foreground": "{{ $onSurface }}",
|
||||
"tab.background": "{{ $surfaceContainerLowest }}",
|
||||
"tab.foreground": "{{ $onSurfaceVariant }}",
|
||||
"tab_bar.background": "{{ $surface }}",
|
||||
"table.background": "{{ $surfaceContainer }}",
|
||||
"table.head.foreground": "{{ $onSurfaceVariant }}",
|
||||
"table.row.border": "{{ $outlineVariant }}",
|
||||
"title_bar.background": "{{ $surfaceDim }}",
|
||||
"ring": "{{ $primary }}",
|
||||
"base.red": "{{ $red }}",
|
||||
"base.red.light": "{{ $peach }}",
|
||||
"base.green": "{{ $green }}",
|
||||
"base.green.light": "{{ $teal }}",
|
||||
"base.blue": "{{ $blue }}",
|
||||
"base.blue.light": "{{ $sky }}",
|
||||
"base.cyan": "{{ $teal }}",
|
||||
"base.cyan.light": "{{ $sky }}",
|
||||
"base.magenta": "{{ $mauve }}",
|
||||
"base.magenta.light": "{{ $pink }}",
|
||||
"base.yellow": "{{ $yellow }}",
|
||||
"base.yellow.light": "{{ $peach }}"
|
||||
},
|
||||
"highlight": {
|
||||
"editor.foreground": "{{ $onSurface }}",
|
||||
"editor.background": "{{ $surface }}",
|
||||
"editor.active_line.background": "{{ $surfaceContainerLow }}",
|
||||
"editor.line_number": "{{ $onSurfaceVariant }}",
|
||||
"editor.active_line_number": "{{ $onSurface }}",
|
||||
"editor.invisible": "{{ $outlineVariant }}",
|
||||
"conflict": "{{ $red }}",
|
||||
"created": "{{ $green }}",
|
||||
"deleted": "{{ $red }}",
|
||||
"error": "{{ $error }}",
|
||||
"hidden": "{{ $outline }}",
|
||||
"hint": "{{ $success }}",
|
||||
"ignored": "{{ $outline }}",
|
||||
"info": "{{ $blue }}",
|
||||
"modified": "{{ $yellow }}",
|
||||
"predictive": "{{ $overlay1 }}",
|
||||
"renamed": "{{ $green }}",
|
||||
"success": "{{ $success }}",
|
||||
"unreachable": "{{ $outlineVariant }}",
|
||||
"warning": "{{ $yellow }}",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "{{ $yellow }}"
|
||||
},
|
||||
"boolean": {
|
||||
"color": "{{ $green }}"
|
||||
},
|
||||
"comment": {
|
||||
"color": "{{ $subtext0 }}",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "{{ $subtext0 }}",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"constant": {
|
||||
"color": "{{ $red }}"
|
||||
},
|
||||
"constructor": {
|
||||
"color": "{{ $yellow }}"
|
||||
},
|
||||
"embedded": {
|
||||
"color": "{{ $onSurface }}"
|
||||
},
|
||||
"function": {
|
||||
"color": "{{ $green }}"
|
||||
},
|
||||
"keyword": {
|
||||
"color": "{{ $mauve }}"
|
||||
},
|
||||
"link_text": {
|
||||
"color": "{{ $sky }}",
|
||||
"font_style": "normal"
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "{{ $klink }}",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"number": {
|
||||
"color": "{{ $red }}"
|
||||
},
|
||||
"string": {
|
||||
"color": "{{ $green }}"
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "{{ $green }}"
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "{{ $green }}"
|
||||
},
|
||||
"string.special": {
|
||||
"color": "{{ $yellow }}"
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "{{ $yellow }}"
|
||||
},
|
||||
"tag": {
|
||||
"color": "{{ $yellow }}"
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "{{ $red }}"
|
||||
},
|
||||
"title": {
|
||||
"color": "{{ $sky }}",
|
||||
"font_weight": 600
|
||||
},
|
||||
"type": {
|
||||
"color": "{{ $yellow }}"
|
||||
},
|
||||
"property": {
|
||||
"color": "{{ $onSurface }}"
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "{{ $red }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[Appearance]
|
||||
color_scheme_path={{ $config }}/colors/caelestia.colors
|
||||
custom_palette=true
|
||||
icon_theme=Papirus-{{ $mode }}
|
||||
standard_dialogs=default
|
||||
style=Darkly
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"theme": {
|
||||
"colorScheme": "~/.config/qtengine/caelestia.colors",
|
||||
"iconTheme": "Papirus-{{ $mode }}",
|
||||
"style": "Darkly",
|
||||
"font": {
|
||||
"family": "Sans Serif",
|
||||
"size": 12,
|
||||
"weight": -1
|
||||
},
|
||||
"fontFixed": {
|
||||
"family": "Monospace",
|
||||
"size": 12,
|
||||
"weight": -1
|
||||
}
|
||||
},
|
||||
"misc": {
|
||||
"menusHaveIcons": true,
|
||||
"singleClickActivate": false,
|
||||
"shortcutsForContextMenus": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
{
|
||||
"$schema": "https://zed.dev/schema/themes/v0.2.0.json",
|
||||
"name": "Caelestia",
|
||||
"author": "Caelestia",
|
||||
"themes": [
|
||||
{
|
||||
"name": "Caelestia",
|
||||
"appearance": "{{ mode }}",
|
||||
"style": {
|
||||
"background": "#{{ surface.hex }}",
|
||||
"border": "#{{ outlineVariant.hex }}40",
|
||||
"border.variant": "#{{ outlineVariant.hex }}60",
|
||||
"border.focused": "#{{ primary.hex }}",
|
||||
"border.selected": "#{{ primary.hex }}80",
|
||||
"border.transparent": "#00000000",
|
||||
"border.disabled": "#{{ outlineVariant.hex }}30",
|
||||
|
||||
"elevated_surface.background": "#{{ surfaceContainerHigh.hex }}",
|
||||
"surface.background": "#{{ surface.hex }}",
|
||||
|
||||
"element.background": "#{{ outlineVariant.hex }}40",
|
||||
"element.hover": "#{{ outlineVariant.hex }}60",
|
||||
"element.active": "#{{ primary.hex }}30",
|
||||
"element.selected": "#{{ primary.hex }}20",
|
||||
"element.disabled": "#{{ outlineVariant.hex }}20",
|
||||
|
||||
"drop_target.background": "#{{ primary.hex }}20",
|
||||
|
||||
"ghost_element.background": "#00000000",
|
||||
"ghost_element.hover": "#{{ outlineVariant.hex }}40",
|
||||
"ghost_element.active": "#{{ primary.hex }}30",
|
||||
"ghost_element.selected": "#{{ primary.hex }}20",
|
||||
"ghost_element.disabled": "#{{ outlineVariant.hex }}20",
|
||||
|
||||
"text": "#{{ onSurface.hex }}",
|
||||
"text.muted": "#{{ onSurfaceVariant.hex }}",
|
||||
"text.placeholder": "#{{ outline.hex }}",
|
||||
"text.disabled": "#{{ outline.hex }}80",
|
||||
"text.accent": "#{{ primary.hex }}",
|
||||
|
||||
"icon": "#{{ onSurface.hex }}",
|
||||
"icon.muted": "#{{ onSurfaceVariant.hex }}",
|
||||
"icon.disabled": "#{{ outlineVariant.hex }}60",
|
||||
"icon.placeholder": "#{{ onSurfaceVariant.hex }}",
|
||||
"icon.accent": "#{{ primary.hex }}",
|
||||
|
||||
"status_bar.background": "#{{ surface.hex }}",
|
||||
"title_bar.background": "#{{ surface.hex }}",
|
||||
"title_bar.inactive_background": "#{{ surface.hex }}",
|
||||
"toolbar.background": "#{{ surface.hex }}",
|
||||
"tab_bar.background": "#{{ surface.hex }}",
|
||||
"tab.inactive_background": "#{{ surface.hex }}",
|
||||
"tab.active_background": "#{{ surfaceContainerHigh.hex }}",
|
||||
|
||||
"search.match_background": "#{{ yellow.hex }}40",
|
||||
|
||||
"panel.background": "#{{ surface.hex }}",
|
||||
"panel.focused_border": "#{{ primary.hex }}",
|
||||
|
||||
"pane.focused_border": "#{{ primary.hex }}",
|
||||
|
||||
"scrollbar.thumb.background": "#{{ outlineVariant.hex }}30",
|
||||
"scrollbar.thumb.hover_background": "#{{ outlineVariant.hex }}60",
|
||||
"scrollbar.thumb.border": "#{{ outlineVariant.hex }}20",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#00000000",
|
||||
|
||||
"editor.foreground": "#{{ onSurface.hex }}",
|
||||
"editor.background": "#{{ surface.hex }}",
|
||||
"editor.gutter.background": "#{{ surface.hex }}",
|
||||
"editor.subheader.background": "#{{ surfaceContainer.hex }}",
|
||||
"editor.active_line.background": "#{{ surfaceContainerHigh.hex }}60",
|
||||
"editor.highlighted_line.background": "#{{ primary.hex }}15",
|
||||
"editor.line_number": "#{{ onSurfaceVariant.hex }}",
|
||||
"editor.active_line_number": "#{{ onSurface.hex }}",
|
||||
"editor.invisible": "#{{ outlineVariant.hex }}40",
|
||||
"editor.wrap_guide": "#{{ outlineVariant.hex }}30",
|
||||
"editor.active_wrap_guide": "#{{ outlineVariant.hex }}60",
|
||||
"editor.document_highlight.read_background": "#{{ primary.hex }}20",
|
||||
"editor.document_highlight.write_background": "#{{ primary.hex }}30",
|
||||
|
||||
"terminal.background": "#{{ surface.hex }}",
|
||||
"terminal.foreground": "#{{ onSurface.hex }}",
|
||||
"terminal.bright_foreground": "#{{ onSurface.hex }}",
|
||||
"terminal.dim_foreground": "#{{ onSurfaceVariant.hex }}",
|
||||
"terminal.ansi.black": "#{{ surface.hex }}",
|
||||
"terminal.ansi.bright_black": "#{{ onSurfaceVariant.hex }}",
|
||||
"terminal.ansi.dim_black": "#{{ surface.hex }}80",
|
||||
"terminal.ansi.red": "#{{ red.hex }}",
|
||||
"terminal.ansi.bright_red": "#{{ maroon.hex }}",
|
||||
"terminal.ansi.dim_red": "#{{ red.hex }}80",
|
||||
"terminal.ansi.green": "#{{ green.hex }}",
|
||||
"terminal.ansi.bright_green": "#{{ teal.hex }}",
|
||||
"terminal.ansi.dim_green": "#{{ green.hex }}80",
|
||||
"terminal.ansi.yellow": "#{{ yellow.hex }}",
|
||||
"terminal.ansi.bright_yellow": "#{{ peach.hex }}",
|
||||
"terminal.ansi.dim_yellow": "#{{ yellow.hex }}80",
|
||||
"terminal.ansi.blue": "#{{ blue.hex }}",
|
||||
"terminal.ansi.bright_blue": "#{{ sapphire.hex }}",
|
||||
"terminal.ansi.dim_blue": "#{{ blue.hex }}80",
|
||||
"terminal.ansi.magenta": "#{{ mauve.hex }}",
|
||||
"terminal.ansi.bright_magenta": "#{{ pink.hex }}",
|
||||
"terminal.ansi.dim_magenta": "#{{ mauve.hex }}80",
|
||||
"terminal.ansi.cyan": "#{{ teal.hex }}",
|
||||
"terminal.ansi.bright_cyan": "#{{ sky.hex }}",
|
||||
"terminal.ansi.dim_cyan": "#{{ teal.hex }}80",
|
||||
"terminal.ansi.white": "#{{ onSurface.hex }}",
|
||||
"terminal.ansi.bright_white": "#{{ onSurface.hex }}",
|
||||
"terminal.ansi.dim_white": "#{{ onSurface.hex }}80",
|
||||
|
||||
"link_text.hover": "#{{ primary.hex }}",
|
||||
|
||||
"conflict": "#{{ yellow.hex }}",
|
||||
"conflict.background": "#{{ yellow.hex }}15",
|
||||
"conflict.border": "#{{ yellow.hex }}",
|
||||
|
||||
"created": "#{{ green.hex }}",
|
||||
"created.background": "#{{ green.hex }}15",
|
||||
"created.border": "#{{ green.hex }}",
|
||||
|
||||
"deleted": "#{{ red.hex }}",
|
||||
"deleted.background": "#{{ red.hex }}15",
|
||||
"deleted.border": "#{{ red.hex }}",
|
||||
|
||||
"error": "#{{ error.hex }}",
|
||||
"error.background": "#{{ error.hex }}15",
|
||||
"error.border": "#{{ error.hex }}",
|
||||
|
||||
"hidden": "#{{ outline.hex }}",
|
||||
"hidden.background": "#{{ outline.hex }}15",
|
||||
"hidden.border": "#{{ outline.hex }}",
|
||||
|
||||
"hint": "#{{ success.hex }}",
|
||||
"hint.background": "#{{ success.hex }}15",
|
||||
"hint.border": "#{{ success.hex }}",
|
||||
|
||||
"ignored": "#{{ outline.hex }}",
|
||||
"ignored.background": "#{{ outline.hex }}15",
|
||||
"ignored.border": "#{{ outline.hex }}",
|
||||
|
||||
"info": "#{{ blue.hex }}",
|
||||
"info.background": "#{{ blue.hex }}15",
|
||||
"info.border": "#{{ blue.hex }}",
|
||||
|
||||
"modified": "#{{ peach.hex }}",
|
||||
"modified.background": "#{{ peach.hex }}15",
|
||||
"modified.border": "#{{ peach.hex }}",
|
||||
|
||||
"predictive": "#{{ onSurfaceVariant.hex }}",
|
||||
"predictive.background": "#{{ onSurfaceVariant.hex }}15",
|
||||
"predictive.border": "#{{ outlineVariant.hex }}40",
|
||||
|
||||
"renamed": "#{{ teal.hex }}",
|
||||
"renamed.background": "#{{ teal.hex }}15",
|
||||
"renamed.border": "#{{ teal.hex }}",
|
||||
|
||||
"success": "#{{ success.hex }}",
|
||||
"success.background": "#{{ success.hex }}15",
|
||||
"success.border": "#{{ success.hex }}",
|
||||
|
||||
"unreachable": "#{{ outline.hex }}",
|
||||
"unreachable.background": "#{{ outline.hex }}15",
|
||||
"unreachable.border": "#{{ outline.hex }}",
|
||||
|
||||
"warning": "#{{ yellow.hex }}",
|
||||
"warning.background": "#{{ yellow.hex }}15",
|
||||
"warning.border": "#{{ yellow.hex }}",
|
||||
|
||||
"players": [
|
||||
{
|
||||
"cursor": "#{{ onSurface.hex }}",
|
||||
"selection": "#{{ onSurface.hex }}60",
|
||||
"background": "#{{ primary.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ teal.hex }}",
|
||||
"selection": "#{{ teal.hex }}40",
|
||||
"background": "#{{ teal.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ pink.hex }}",
|
||||
"selection": "#{{ pink.hex }}40",
|
||||
"background": "#{{ pink.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ yellow.hex }}",
|
||||
"selection": "#{{ yellow.hex }}40",
|
||||
"background": "#{{ yellow.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ green.hex }}",
|
||||
"selection": "#{{ green.hex }}40",
|
||||
"background": "#{{ green.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ red.hex }}",
|
||||
"selection": "#{{ red.hex }}40",
|
||||
"background": "#{{ red.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ blue.hex }}",
|
||||
"selection": "#{{ blue.hex }}40",
|
||||
"background": "#{{ blue.hex }}"
|
||||
},
|
||||
{
|
||||
"cursor": "#{{ maroon.hex }}",
|
||||
"selection": "#{{ maroon.hex }}40",
|
||||
"background": "#{{ maroon.hex }}"
|
||||
}
|
||||
],
|
||||
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#{{ peach.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#{{ subtext0.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#{{ subtext0.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#{{ peach.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#{{ onSurface.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"emphasis": {
|
||||
"color": "#{{ red.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"emphasis.strong": {
|
||||
"color": "#{{ red.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
},
|
||||
"enum": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#{{ blue.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function.builtin": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function.definition": {
|
||||
"color": "#{{ blue.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function.method": {
|
||||
"color": "#{{ blue.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function.special.definition": {
|
||||
"color": "#{{ blue.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"hint": {
|
||||
"color": "#{{ onSurfaceVariant.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#{{ pink.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"label": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#{{ blue.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": "underline",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#{{ peach.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"operator": {
|
||||
"color": "#{{ sapphire.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"predictive": {
|
||||
"color": "#{{ onSurfaceVariant.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"preproc": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"primary": {
|
||||
"color": "#{{ onSurface.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"property": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation": {
|
||||
"color": "#{{ subtext1.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation.bracket": {
|
||||
"color": "#{{ subtext1.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation.delimiter": {
|
||||
"color": "#{{ subtext1.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation.list_marker": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation.special": {
|
||||
"color": "#{{ sapphire.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#{{ green.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#{{ pink.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#{{ sky.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#{{ green.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#{{ green.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#{{ blue.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
},
|
||||
"type": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"type.builtin": {
|
||||
"color": "#{{ onSurface.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"type.interface": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"type.super": {
|
||||
"color": "#{{ yellow.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#{{ onSurface.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.member": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.parameter": {
|
||||
"color": "#{{ teal.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#{{ onSurface.hex }}",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"variant": {
|
||||
"color": "#{{ peach.hex }}",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,7 @@ from caelestia.utils.scheme import get_scheme_names, scheme_variants
|
||||
from caelestia.utils.wallpaper import get_wallpaper
|
||||
|
||||
|
||||
def parse_args() -> (argparse.ArgumentParser, argparse.Namespace):
|
||||
def parse_args() -> tuple[argparse.ArgumentParser, argparse.Namespace]:
|
||||
parser = argparse.ArgumentParser(prog="caelestia", description="Main control script for the Caelestia dotfiles")
|
||||
parser.add_argument("-v", "--version", action="store_true", help="print the current version")
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
@@ -6,6 +5,7 @@ import subprocess
|
||||
import time
|
||||
from argparse import Namespace
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from caelestia.utils.notify import close_notification, notify
|
||||
from caelestia.utils.paths import recording_notif_path, recording_path, recordings_dir, user_config_path
|
||||
|
||||
@@ -29,8 +29,6 @@ class Command:
|
||||
def _load_window_rules(self) -> list[WindowRule]:
|
||||
default_rules = [
|
||||
WindowRule("(Bitwarden", "titleContains", "20%", "54%", ["float", "center"]),
|
||||
WindowRule("Sign in - Google Accounts", "titleContains", "35%", "65%", ["float", "center"]),
|
||||
WindowRule("oauth", "titleContains", "30%", "60%", ["float", "center"]),
|
||||
WindowRule("^[Pp]icture(-| )in(-| )[Pp]icture$", "titleRegex", "", "", ["pip"]),
|
||||
]
|
||||
|
||||
@@ -140,9 +138,12 @@ class Command:
|
||||
monitor_x = monitor.get("x")
|
||||
monitor_y = monitor.get("y")
|
||||
|
||||
if not all(isinstance(x, (int, float)) for x in [monitor_height, monitor_width, monitor_scale, monitor_x, monitor_y]):
|
||||
if not all(
|
||||
isinstance(x, (int, float))
|
||||
for x in [monitor_height, monitor_width, monitor_scale, monitor_x, monitor_y]
|
||||
):
|
||||
return
|
||||
|
||||
|
||||
monitor_height = monitor_height / monitor_scale
|
||||
monitor_width = monitor_width / monitor_scale
|
||||
|
||||
@@ -232,7 +233,7 @@ class Command:
|
||||
window_id = event.split(">>>")[1].split(",")[0]
|
||||
else:
|
||||
window_id = event.split(">>")[1].split(",")[0]
|
||||
|
||||
|
||||
# Remove any leading > characters
|
||||
window_id = window_id.lstrip(">")
|
||||
|
||||
@@ -268,9 +269,9 @@ class Command:
|
||||
data = event[13:] # Remove "openwindow>>>"
|
||||
else:
|
||||
data = event[12:] # Remove "openwindow>>"
|
||||
|
||||
|
||||
window_id, workspace, window_class, title = data.split(",", 3)
|
||||
|
||||
|
||||
# Remove any leading > characters
|
||||
window_id = window_id.lstrip(">")
|
||||
|
||||
@@ -348,19 +349,19 @@ class Command:
|
||||
|
||||
# Find all windows that match the pattern
|
||||
matching_windows = self._find_matching_windows(temp_rule)
|
||||
|
||||
|
||||
if not matching_windows:
|
||||
print(f"No windows found matching pattern '{temp_rule.name}' with match type '{temp_rule.match_type}'")
|
||||
return
|
||||
|
||||
print(f"Found {len(matching_windows)} matching window(s)")
|
||||
|
||||
|
||||
# Apply rule to all matching windows
|
||||
success_count = 0
|
||||
for window in matching_windows:
|
||||
window_id = window["address"][2:] # Remove "0x" prefix
|
||||
window_title = window.get("title", "")
|
||||
|
||||
|
||||
print(f"Applying rule to window 0x{window_id}: '{window_title}'")
|
||||
success = self._apply_window_actions(window_id, temp_rule.width, temp_rule.height, temp_rule.actions)
|
||||
if success:
|
||||
@@ -386,7 +387,7 @@ class Command:
|
||||
return
|
||||
|
||||
window_id = address[2:] # Remove "0x" prefix
|
||||
|
||||
|
||||
print(f"Applying rule to active window 0x{window_id}: '{window_title}'")
|
||||
success = self._apply_window_actions(window_id, temp_rule.width, temp_rule.height, temp_rule.actions)
|
||||
if success:
|
||||
@@ -411,7 +412,7 @@ class Command:
|
||||
|
||||
window_title = window.get("title", "")
|
||||
initial_title = window.get("initialTitle", "")
|
||||
|
||||
|
||||
# Check if window matches the pattern
|
||||
matches = False
|
||||
if temp_rule.match_type == "initialTitle":
|
||||
|
||||
@@ -26,8 +26,11 @@ class Command:
|
||||
else:
|
||||
sc_data = subprocess.check_output(["grim", "-l", "0", "-g", self.args.region.strip(), "-"])
|
||||
swappy = subprocess.Popen(["swappy", "-f", "-"], stdin=subprocess.PIPE, start_new_session=True)
|
||||
swappy.stdin.write(sc_data)
|
||||
swappy.stdin.close()
|
||||
|
||||
# Ensure stdin is not None for the type checker
|
||||
if swappy.stdin:
|
||||
swappy.stdin.write(sc_data)
|
||||
swappy.stdin.close()
|
||||
|
||||
def fullscreen(self) -> None:
|
||||
sc_data = subprocess.check_output(["grim", "-"])
|
||||
|
||||
@@ -33,11 +33,14 @@ class Command:
|
||||
subprocess.run(args)
|
||||
else:
|
||||
shell = subprocess.Popen(args, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
for line in shell.stdout:
|
||||
if self.filter_log(line):
|
||||
print(line, end="")
|
||||
|
||||
def shell(self, *args: list[str]) -> str:
|
||||
# Ensure stdout is not None for the type checker
|
||||
if shell.stdout:
|
||||
for line in shell.stdout:
|
||||
if self.filter_log(line):
|
||||
print(line, end="")
|
||||
|
||||
def shell(self, *args: str) -> str:
|
||||
return subprocess.check_output(["qs", "-c", "caelestia", *args], text=True)
|
||||
|
||||
def filter_log(self, line: str) -> bool:
|
||||
|
||||
@@ -3,6 +3,7 @@ import shlex
|
||||
import shutil
|
||||
from argparse import Namespace
|
||||
from collections import ChainMap
|
||||
from typing import Any, Callable, cast
|
||||
|
||||
from caelestia.utils import hypr
|
||||
from caelestia.utils.paths import user_config_path
|
||||
@@ -52,8 +53,8 @@ class DeepChainMap(ChainMap):
|
||||
|
||||
class Command:
|
||||
args: Namespace
|
||||
cfg: dict[str, dict[str, dict[str, any]]] | DeepChainMap
|
||||
clients: list[dict[str, any]] = None
|
||||
cfg: dict[str, dict[str, dict[str, Any]]] | DeepChainMap
|
||||
clients: list[dict[str, Any]] | None = None
|
||||
|
||||
def __init__(self, args: Namespace) -> None:
|
||||
self.args = args
|
||||
@@ -120,27 +121,27 @@ class Command:
|
||||
if not spawned:
|
||||
hypr.dispatch("togglespecialworkspace", self.args.workspace)
|
||||
|
||||
def get_clients(self) -> list[dict[str, any]]:
|
||||
def get_clients(self) -> list[dict[str, Any]]:
|
||||
if self.clients is None:
|
||||
self.clients = hypr.message("clients")
|
||||
|
||||
self.clients = cast(list[dict[str, Any]], hypr.message("clients"))
|
||||
return self.clients
|
||||
|
||||
def move_client(self, selector: callable, workspace: str) -> None:
|
||||
def move_client(self, selector: Callable, workspace: str) -> None:
|
||||
for client in self.get_clients():
|
||||
if selector(client) and client["workspace"]["name"] != f"special:{workspace}":
|
||||
hypr.dispatch("movetoworkspacesilent", f"special:{workspace},address:{client['address']}")
|
||||
|
||||
def spawn_client(self, selector: callable, spawn: list[str]) -> bool:
|
||||
def spawn_client(self, selector: Callable, spawn: list[str]) -> bool:
|
||||
if (spawn[0].endswith(".desktop") or shutil.which(spawn[0])) and not any(
|
||||
selector(client) for client in self.get_clients()
|
||||
):
|
||||
hypr.dispatch("exec", f"[workspace special:{self.args.workspace}] app2unit -- {shlex.join(spawn)}")
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def handle_client_config(self, client: dict[str, any]) -> bool:
|
||||
def selector(c: dict[str, any]) -> bool:
|
||||
def handle_client_config(self, client: dict[str, Any]) -> bool:
|
||||
def selector(c: dict[str, Any]) -> bool:
|
||||
# Each match is or, inside matches is and
|
||||
for match in client["match"]:
|
||||
if is_subset(c, match):
|
||||
@@ -156,5 +157,8 @@ class Command:
|
||||
return spawned
|
||||
|
||||
def specialws(self) -> None:
|
||||
special = next(m for m in hypr.message("monitors") if m["focused"])["specialWorkspace"]["name"]
|
||||
hypr.dispatch("togglespecialworkspace", special[8:] or "special")
|
||||
monitors = cast(list[dict[str, Any]], hypr.message("monitors"))
|
||||
target = next((m for m in monitors if m.get("focused")), None)
|
||||
if target:
|
||||
special = target.get("specialWorkspace", {}).get("name", "")[8:] or "special"
|
||||
hypr.dispatch("togglespecialworkspace", special)
|
||||
|
||||
@@ -11,8 +11,7 @@ def stddev(values: list[float], mean_val: float) -> float:
|
||||
return math.sqrt(sum((x - mean_val) ** 2 for x in values) / len(values)) if values else 0
|
||||
|
||||
|
||||
def calc_colourfulness(image: Image) -> float:
|
||||
width, height = image.size
|
||||
def calc_colourfulness(image: Image.Image) -> float:
|
||||
pixels = list(image.getdata()) # List of (R, G, B) tuples
|
||||
|
||||
rg_diffs = []
|
||||
@@ -32,7 +31,7 @@ def calc_colourfulness(image: Image) -> float:
|
||||
return math.sqrt(std_rg**2 + std_yb**2) + 0.3 * math.sqrt(mean_rg**2 + mean_yb**2)
|
||||
|
||||
|
||||
def get_variant(image: Image) -> str:
|
||||
def get_variant(image: Image.Image) -> str:
|
||||
colourfulness = calc_colourfulness(image)
|
||||
|
||||
if colourfulness < 10:
|
||||
|
||||
+14
-10
@@ -1,17 +1,18 @@
|
||||
import json as j
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
from typing import Any
|
||||
|
||||
socket_base = f"{os.getenv('XDG_RUNTIME_DIR')}/hypr/{os.getenv('HYPRLAND_INSTANCE_SIGNATURE')}"
|
||||
socket_path = f"{socket_base}/.socket.sock"
|
||||
socket2_path = f"{socket_base}/.socket2.sock"
|
||||
|
||||
|
||||
def message(msg: str, json: bool = True) -> str | dict[str, any]:
|
||||
def message(msg: str, is_json: bool = True) -> str | dict[str, Any]:
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
|
||||
sock.connect(socket_path)
|
||||
|
||||
if json:
|
||||
if is_json:
|
||||
msg = f"j/{msg}"
|
||||
sock.send(msg.encode())
|
||||
|
||||
@@ -22,14 +23,17 @@ def message(msg: str, json: bool = True) -> str | dict[str, any]:
|
||||
break
|
||||
resp += new_resp.decode()
|
||||
|
||||
return j.loads(resp) if json else resp
|
||||
return json.loads(resp) if is_json else resp
|
||||
|
||||
|
||||
def dispatch(dispatcher: str, *args: list[any]) -> bool:
|
||||
return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), json=False) == "ok"
|
||||
def dispatch(dispatcher: str, *args: str) -> bool:
|
||||
return message(f"dispatch {dispatcher} {' '.join(map(str, args))}".rstrip(), is_json=False) == "ok"
|
||||
|
||||
|
||||
def batch(*msgs: list[str], json: bool = False) -> str | dict[str, any]:
|
||||
if json:
|
||||
msgs = (f"j/{m.strip()}" for m in msgs)
|
||||
return message(f"[[BATCH]]{';'.join(msgs)}", json=False)
|
||||
def batch(*msgs: str, is_json: bool = False) -> str | dict[str, Any]:
|
||||
formatted_msgs = msgs
|
||||
|
||||
if is_json:
|
||||
formatted_msgs = [f"j/{m.strip()}" for m in msgs]
|
||||
|
||||
return message(f"[[BATCH]]{';'.join(formatted_msgs)}", is_json=False)
|
||||
|
||||
@@ -12,9 +12,11 @@ def log_exception(func):
|
||||
Used by the `apply_()` functions so that an exception, when applying
|
||||
a theme, does not prevent the other themes from being applied.
|
||||
"""
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
log_message(f'Error during execution of "{func.__name__}()": {str(e)}')
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
from materialyoucolor.blend import Blend
|
||||
from materialyoucolor.dynamiccolor.material_dynamic_colors import (
|
||||
DynamicScheme,
|
||||
MaterialDynamicColors,
|
||||
)
|
||||
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
|
||||
from materialyoucolor.hct import Hct
|
||||
from materialyoucolor.scheme.scheme_content import SchemeContent
|
||||
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive
|
||||
@@ -14,6 +11,19 @@ from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow
|
||||
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
|
||||
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
|
||||
from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double
|
||||
from typing import Protocol, Any
|
||||
|
||||
|
||||
# The base DynamicScheme class requires a 'variant' argument, but the specific
|
||||
# subclasses in get_scheme() handle that internally. This Protocol tells the type
|
||||
# checker to expect our specific 3-argument setup instead of the base class signature.
|
||||
class SchemeConstructor(Protocol):
|
||||
def __call__(self, source_color_hct: Any, is_dark: bool, contrast_level: float) -> DynamicScheme: ...
|
||||
|
||||
try:
|
||||
from materialyoucolor.dynamiccolor.dynamic_scheme import DynamicScheme
|
||||
except ImportError:
|
||||
from materialyoucolor.scheme.dynamic_scheme import DynamicScheme
|
||||
|
||||
|
||||
def hex_to_hct(hex: str) -> Hct:
|
||||
@@ -145,7 +155,7 @@ def darken(colour: Hct, amount: float) -> Hct:
|
||||
return Hct.from_hct(colour.hue, colour.chroma - diff / 5, colour.tone - diff)
|
||||
|
||||
|
||||
def get_scheme(scheme: str) -> DynamicScheme:
|
||||
def get_scheme(scheme: str) -> SchemeConstructor:
|
||||
if scheme == "content":
|
||||
return SchemeContent
|
||||
if scheme == "expressive":
|
||||
@@ -166,12 +176,12 @@ def get_scheme(scheme: str) -> DynamicScheme:
|
||||
|
||||
|
||||
def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
light = scheme.mode == "light"
|
||||
is_light = scheme.mode == "light"
|
||||
|
||||
colours = {}
|
||||
|
||||
# Material colours
|
||||
primary_scheme = get_scheme(scheme.variant)(primary, not light, 0)
|
||||
primary_scheme = get_scheme(scheme.variant)(source_color_hct=primary, is_dark=not is_light, contrast_level=0.0)
|
||||
if hasattr(MaterialDynamicColors, "all_colors"): # materialyoucolor-python >= 3.0.0
|
||||
dyn_colours = MaterialDynamicColors()
|
||||
for colour in dyn_colours.all_colors:
|
||||
@@ -189,28 +199,28 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
colours["neutral_variant_paletteKeyColor"] = colours["neutralVariantPaletteKeyColor"]
|
||||
|
||||
# Harmonize terminal colours
|
||||
for i, hct in enumerate(light_gruvbox if light else dark_gruvbox):
|
||||
for i, hct in enumerate(light_gruvbox if is_light else dark_gruvbox):
|
||||
if scheme.variant == "monochrome":
|
||||
colours[f"term{i}"] = grayscale(hct, light)
|
||||
colours[f"term{i}"] = grayscale(hct, is_light)
|
||||
else:
|
||||
colours[f"term{i}"] = harmonize(
|
||||
hct, colours["primary_paletteKeyColor"], (0.35 if i < 8 else 0.2) * (-1 if light else 1)
|
||||
hct, colours["primary_paletteKeyColor"], (0.35 if i < 8 else 0.2) * (-1 if is_light else 1)
|
||||
)
|
||||
|
||||
# Harmonize named colours
|
||||
for i, hct in enumerate(light_catppuccin if light else dark_catppuccin):
|
||||
for i, hct in enumerate(light_catppuccin if is_light else dark_catppuccin):
|
||||
if scheme.variant == "monochrome":
|
||||
colours[colour_names[i]] = grayscale(hct, light)
|
||||
colours[colour_names[i]] = grayscale(hct, is_light)
|
||||
else:
|
||||
colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if light else 0.05))
|
||||
colours[colour_names[i]] = harmonize(hct, colours["primary_paletteKeyColor"], (-0.2 if is_light else 0.05))
|
||||
|
||||
# KColours
|
||||
for colour in kcolours:
|
||||
colours[colour["name"]] = harmonize(colour["hct"], colours["primary"], 0.1)
|
||||
colours[f"{colour['name']}Selection"] = harmonize(colour["hct"], colours["onPrimaryFixedVariant"], 0.1)
|
||||
if scheme.variant == "monochrome":
|
||||
colours[colour["name"]] = grayscale(colours[colour["name"]], light)
|
||||
colours[f"{colour['name']}Selection"] = grayscale(colours[f"{colour['name']}Selection"], light)
|
||||
colours[colour["name"]] = grayscale(colours[colour["name"]], is_light)
|
||||
colours[f"{colour['name']}Selection"] = grayscale(colours[f"{colour['name']}Selection"], is_light)
|
||||
|
||||
if scheme.variant == "neutral":
|
||||
for name, hct in colours.items():
|
||||
@@ -219,8 +229,8 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
# Darken surfaces for hard flavour
|
||||
if scheme.flavour == "hard":
|
||||
for colour in "background", *(k for k in colours.keys() if k.startswith("surface")):
|
||||
colours[colour] = lighten(colours[colour], 0.4) if light else darken(colours[colour], 0.8)
|
||||
colours["term0"] = lighten(colours["term0"], 0.4) if light else darken(colours["term0"], 0.9)
|
||||
colours[colour] = lighten(colours[colour], 0.4) if is_light else darken(colours[colour], 0.8)
|
||||
colours["term0"] = lighten(colours["term0"], 0.4) if is_light else darken(colours["term0"], 0.9)
|
||||
|
||||
# FIXME: deprecated stuff
|
||||
colours["text"] = colours["onBackground"]
|
||||
@@ -239,13 +249,13 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
# More darkening if hard flavour
|
||||
if scheme.flavour == "hard":
|
||||
for colour in "base", "mantle", "crust":
|
||||
colours[colour] = lighten(colours[colour], 0.4) if light else darken(colours[colour], 0.9)
|
||||
colours[colour] = lighten(colours[colour], 0.4) if is_light else darken(colours[colour], 0.9)
|
||||
for i in range(3):
|
||||
colours[f"overlay{i}"] = (
|
||||
lighten(colours[f"overlay{i}"], 0.4) if light else darken(colours[f"overlay{i}"], 0.8)
|
||||
lighten(colours[f"overlay{i}"], 0.4) if is_light else darken(colours[f"overlay{i}"], 0.8)
|
||||
)
|
||||
colours[f"surface{i}"] = (
|
||||
lighten(colours[f"surface{i}"], 0.4) if light else darken(colours[f"surface{i}"], 0.8)
|
||||
lighten(colours[f"surface{i}"], 0.4) if is_light else darken(colours[f"surface{i}"], 0.8)
|
||||
)
|
||||
|
||||
# For debugging
|
||||
@@ -254,7 +264,7 @@ def gen_scheme(scheme, primary: Hct) -> dict[str, str]:
|
||||
colours = {k: hex(v.to_int())[4:] for k, v in colours.items()}
|
||||
|
||||
# Extended material
|
||||
if light:
|
||||
if is_light:
|
||||
colours["success"] = "4F6354"
|
||||
colours["onSuccess"] = "FFFFFF"
|
||||
colours["successContainer"] = "D1E8D5"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import subprocess
|
||||
|
||||
|
||||
def notify(*args: list[str]) -> str:
|
||||
def notify(*args: str) -> str:
|
||||
return subprocess.check_output(["notify-send", "-a", "caelestia-cli", *args], text=True).strip()
|
||||
|
||||
|
||||
|
||||
@@ -4,41 +4,42 @@ import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
config_dir = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
||||
data_dir = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local/share"))
|
||||
state_dir = Path(os.getenv("XDG_STATE_HOME", Path.home() / ".local/state"))
|
||||
cache_dir = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
|
||||
pictures_dir = Path(os.getenv("XDG_PICTURES_DIR", Path.home() / "Pictures"))
|
||||
videos_dir = Path(os.getenv("XDG_VIDEOS_DIR", Path.home() / "Videos"))
|
||||
config_dir: Path = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
||||
data_dir: Path = Path(os.getenv("XDG_DATA_HOME", Path.home() / ".local/share"))
|
||||
state_dir: Path = Path(os.getenv("XDG_STATE_HOME", Path.home() / ".local/state"))
|
||||
cache_dir: Path = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache"))
|
||||
pictures_dir: Path = Path(os.getenv("XDG_PICTURES_DIR", Path.home() / "Pictures"))
|
||||
videos_dir: Path = Path(os.getenv("XDG_VIDEOS_DIR", Path.home() / "Videos"))
|
||||
|
||||
c_config_dir = config_dir / "caelestia"
|
||||
c_data_dir = data_dir / "caelestia"
|
||||
c_state_dir = state_dir / "caelestia"
|
||||
c_cache_dir = cache_dir / "caelestia"
|
||||
c_config_dir: Path = config_dir / "caelestia"
|
||||
c_data_dir: Path = data_dir / "caelestia"
|
||||
c_state_dir: Path = state_dir / "caelestia"
|
||||
c_cache_dir: Path = cache_dir / "caelestia"
|
||||
|
||||
user_config_path = c_config_dir / "cli.json"
|
||||
cli_data_dir = Path(__file__).parent.parent / "data"
|
||||
templates_dir = cli_data_dir / "templates"
|
||||
user_templates_dir = c_config_dir / "templates"
|
||||
theme_dir = c_state_dir / "theme"
|
||||
user_config_path: Path = c_config_dir / "cli.json"
|
||||
cli_data_dir: Path = Path(__file__).parent.parent / "data"
|
||||
templates_dir: Path = cli_data_dir / "templates"
|
||||
user_templates_dir: Path = c_config_dir / "templates"
|
||||
theme_dir: Path = c_state_dir / "theme"
|
||||
|
||||
scheme_path = c_state_dir / "scheme.json"
|
||||
scheme_data_dir = cli_data_dir / "schemes"
|
||||
scheme_cache_dir = c_cache_dir / "schemes"
|
||||
scheme_path: Path = c_state_dir / "scheme.json"
|
||||
scheme_data_dir: Path = cli_data_dir / "schemes"
|
||||
scheme_cache_dir: Path = c_cache_dir / "schemes"
|
||||
|
||||
wallpapers_dir = os.getenv("CAELESTIA_WALLPAPERS_DIR", pictures_dir / "Wallpapers")
|
||||
wallpaper_path_path = c_state_dir / "wallpaper/path.txt"
|
||||
wallpaper_link_path = c_state_dir / "wallpaper/current"
|
||||
wallpaper_thumbnail_path = c_state_dir / "wallpaper/thumbnail.jpg"
|
||||
wallpapers_cache_dir = c_cache_dir / "wallpapers"
|
||||
wallpapers_dir: Path = Path(os.getenv("CAELESTIA_WALLPAPERS_DIR", pictures_dir / "Wallpapers"))
|
||||
wallpaper_path_path: Path = c_state_dir / "wallpaper/path.txt"
|
||||
wallpaper_link_path: Path = c_state_dir / "wallpaper/current"
|
||||
wallpaper_thumbnail_path: Path = c_state_dir / "wallpaper/thumbnail.jpg"
|
||||
wallpapers_cache_dir: Path = c_cache_dir / "wallpapers"
|
||||
|
||||
screenshots_dir = os.getenv("CAELESTIA_SCREENSHOTS_DIR", pictures_dir / "Screenshots")
|
||||
screenshots_cache_dir = c_cache_dir / "screenshots"
|
||||
screenshots_dir: Path = Path(os.getenv("CAELESTIA_SCREENSHOTS_DIR", pictures_dir / "Screenshots"))
|
||||
screenshots_cache_dir: Path = c_cache_dir / "screenshots"
|
||||
|
||||
recordings_dir = os.getenv("CAELESTIA_RECORDINGS_DIR", videos_dir / "Recordings")
|
||||
recording_path = c_state_dir / "record/recording.mp4"
|
||||
recording_notif_path = c_state_dir / "record/notifid.txt"
|
||||
recordings_dir: Path = Path(os.getenv("CAELESTIA_RECORDINGS_DIR", videos_dir / "Recordings"))
|
||||
recording_path: Path = c_state_dir / "record/recording.mp4"
|
||||
recording_notif_path: Path = c_state_dir / "record/notifid.txt"
|
||||
|
||||
|
||||
def compute_hash(path: Path | str) -> str:
|
||||
@@ -51,7 +52,7 @@ def compute_hash(path: Path | str) -> str:
|
||||
return sha.hexdigest()
|
||||
|
||||
|
||||
def atomic_dump(path: Path, content: dict[str, any]) -> None:
|
||||
def atomic_dump(path: Path, content: dict[str, Any]) -> None:
|
||||
with tempfile.NamedTemporaryFile("w") as f:
|
||||
json.dump(content, f)
|
||||
f.flush()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
import random
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from caelestia.utils.notify import notify
|
||||
from caelestia.utils.paths import atomic_dump, scheme_data_dir, scheme_path
|
||||
@@ -14,19 +15,19 @@ class Scheme:
|
||||
_colours: dict[str, str]
|
||||
notify: bool
|
||||
|
||||
def __init__(self, json: dict[str, any] | None) -> None:
|
||||
if json is None:
|
||||
def __init__(self, scheme_json: dict[str, Any] | None) -> None:
|
||||
if scheme_json is None:
|
||||
self._name = "catppuccin"
|
||||
self._flavour = "mocha"
|
||||
self._mode = "dark"
|
||||
self._variant = "tonalspot"
|
||||
self._colours = read_colours_from_file(self.get_colours_path())
|
||||
else:
|
||||
self._name = json["name"]
|
||||
self._flavour = json["flavour"]
|
||||
self._mode = json["mode"]
|
||||
self._variant = json["variant"]
|
||||
self._colours = json["colours"]
|
||||
self._name = scheme_json["name"]
|
||||
self._flavour = scheme_json["flavour"]
|
||||
self._mode = scheme_json["mode"]
|
||||
self._variant = scheme_json["variant"]
|
||||
self._colours = scheme_json["colours"]
|
||||
self.notify = False
|
||||
|
||||
@property
|
||||
@@ -196,7 +197,7 @@ scheme_variants = [
|
||||
"content",
|
||||
]
|
||||
|
||||
scheme: Scheme = None
|
||||
scheme: Scheme | None = None
|
||||
|
||||
|
||||
def read_colours_from_file(path: Path) -> dict[str, str]:
|
||||
@@ -225,7 +226,7 @@ def get_scheme_names() -> list[str]:
|
||||
return [*(f.name for f in scheme_data_dir.iterdir() if f.is_dir()), "dynamic"]
|
||||
|
||||
|
||||
def get_scheme_flavours(name: str = None) -> list[str]:
|
||||
def get_scheme_flavours(name: str | None = None) -> list[str]:
|
||||
if name is None:
|
||||
name = get_scheme().name
|
||||
|
||||
@@ -234,11 +235,11 @@ def get_scheme_flavours(name: str = None) -> list[str]:
|
||||
)
|
||||
|
||||
|
||||
def get_scheme_modes(name: str = None, flavour: str = None) -> list[str]:
|
||||
if name is None:
|
||||
def get_scheme_modes(name: str | None = None, flavour: str | None = None) -> list[str]:
|
||||
if name is None or flavour is None:
|
||||
scheme = get_scheme()
|
||||
name = scheme.name
|
||||
flavour = scheme.flavour
|
||||
name = name or scheme.name
|
||||
flavour = flavour or scheme.flavour
|
||||
|
||||
if name == "dynamic":
|
||||
return ["light", "dark"]
|
||||
|
||||
+112
-74
@@ -1,11 +1,10 @@
|
||||
import fcntl
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
import fcntl
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from caelestia.utils.colour import get_dynamic_colours
|
||||
from caelestia.utils.logging import log_exception
|
||||
@@ -35,10 +34,10 @@ def gen_scss(colours: dict[str, str]) -> str:
|
||||
|
||||
|
||||
def gen_replace(colours: dict[str, str], template: Path, hash: bool = False) -> str:
|
||||
template = template.read_text()
|
||||
new_template = template.read_text()
|
||||
for name, colour in colours.items():
|
||||
template = template.replace(f"{{{{ ${name} }}}}", f"#{colour}" if hash else colour)
|
||||
return template
|
||||
new_template = new_template.replace(f"{{{{ ${name} }}}}", f"#{colour}" if hash else colour)
|
||||
return new_template
|
||||
|
||||
|
||||
def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> str:
|
||||
@@ -60,13 +59,13 @@ def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> s
|
||||
colours_dyn = get_dynamic_colours(colours)
|
||||
template_content = template.read_text()
|
||||
|
||||
template_filled = re.sub(dotField, fill_colour, template_content)
|
||||
template_filled = re.sub(dotField, fill_colour, template_content)
|
||||
template_filled = re.sub(modeField, mode, template_filled)
|
||||
|
||||
return template_filled
|
||||
|
||||
|
||||
def c2s(c: str, *i: list[int]) -> str:
|
||||
def hex_to_ansi(c: str, *i: int) -> str:
|
||||
"""Hex to ANSI sequence (e.g. ffffff, 11 -> \x1b]11;rgb:ff/ff/ff\x1b\\)"""
|
||||
return f"\x1b]{';'.join(map(str, i))};rgb:{c[0:2]}/{c[2:4]}/{c[4:6]}\x1b\\"
|
||||
|
||||
@@ -83,29 +82,29 @@ def gen_sequences(colours: dict[str, str]) -> str:
|
||||
16+: 256 colours
|
||||
"""
|
||||
return (
|
||||
c2s(colours["onSurface"], 10)
|
||||
+ c2s(colours["surface"], 11)
|
||||
+ c2s(colours["secondary"], 12)
|
||||
+ c2s(colours["secondary"], 17)
|
||||
+ c2s(colours["term0"], 4, 0)
|
||||
+ c2s(colours["term1"], 4, 1)
|
||||
+ c2s(colours["term2"], 4, 2)
|
||||
+ c2s(colours["term3"], 4, 3)
|
||||
+ c2s(colours["term4"], 4, 4)
|
||||
+ c2s(colours["term5"], 4, 5)
|
||||
+ c2s(colours["term6"], 4, 6)
|
||||
+ c2s(colours["term7"], 4, 7)
|
||||
+ c2s(colours["term8"], 4, 8)
|
||||
+ c2s(colours["term9"], 4, 9)
|
||||
+ c2s(colours["term10"], 4, 10)
|
||||
+ c2s(colours["term11"], 4, 11)
|
||||
+ c2s(colours["term12"], 4, 12)
|
||||
+ c2s(colours["term13"], 4, 13)
|
||||
+ c2s(colours["term14"], 4, 14)
|
||||
+ c2s(colours["term15"], 4, 15)
|
||||
+ c2s(colours["primary"], 4, 16)
|
||||
+ c2s(colours["secondary"], 4, 17)
|
||||
+ c2s(colours["tertiary"], 4, 18)
|
||||
hex_to_ansi(colours["onSurface"], 10)
|
||||
+ hex_to_ansi(colours["surface"], 11)
|
||||
+ hex_to_ansi(colours["secondary"], 12)
|
||||
+ hex_to_ansi(colours["secondary"], 17)
|
||||
+ hex_to_ansi(colours["term0"], 4, 0)
|
||||
+ hex_to_ansi(colours["term1"], 4, 1)
|
||||
+ hex_to_ansi(colours["term2"], 4, 2)
|
||||
+ hex_to_ansi(colours["term3"], 4, 3)
|
||||
+ hex_to_ansi(colours["term4"], 4, 4)
|
||||
+ hex_to_ansi(colours["term5"], 4, 5)
|
||||
+ hex_to_ansi(colours["term6"], 4, 6)
|
||||
+ hex_to_ansi(colours["term7"], 4, 7)
|
||||
+ hex_to_ansi(colours["term8"], 4, 8)
|
||||
+ hex_to_ansi(colours["term9"], 4, 9)
|
||||
+ hex_to_ansi(colours["term10"], 4, 10)
|
||||
+ hex_to_ansi(colours["term11"], 4, 11)
|
||||
+ hex_to_ansi(colours["term12"], 4, 12)
|
||||
+ hex_to_ansi(colours["term13"], 4, 13)
|
||||
+ hex_to_ansi(colours["term14"], 4, 14)
|
||||
+ hex_to_ansi(colours["term15"], 4, 15)
|
||||
+ hex_to_ansi(colours["primary"], 4, 16)
|
||||
+ hex_to_ansi(colours["secondary"], 4, 17)
|
||||
+ hex_to_ansi(colours["tertiary"], 4, 18)
|
||||
)
|
||||
|
||||
|
||||
@@ -117,6 +116,7 @@ def write_file(path: Path, content: str) -> None:
|
||||
f.flush()
|
||||
shutil.move(f.name, path)
|
||||
|
||||
|
||||
@log_exception
|
||||
def apply_terms(sequences: str) -> None:
|
||||
state = c_state_dir / "sequences.txt"
|
||||
@@ -129,6 +129,7 @@ def apply_terms(sequences: str) -> None:
|
||||
try:
|
||||
# Use non-blocking write with timeout to prevent hangs
|
||||
import os
|
||||
|
||||
fd = os.open(str(pt), os.O_WRONLY | os.O_NONBLOCK | os.O_NOCTTY)
|
||||
try:
|
||||
os.write(fd, sequences.encode())
|
||||
@@ -156,6 +157,13 @@ def apply_discord(scss: str) -> None:
|
||||
write_file(config_dir / client / "themes/caelestia.theme.css", conf)
|
||||
|
||||
|
||||
@log_exception
|
||||
def apply_pandora(colours: dict[str, str], mode: str) -> None:
|
||||
template = gen_replace(colours, templates_dir / "pandora.json", hash=True)
|
||||
template = template.replace("{{ $mode }}", mode)
|
||||
write_file(data_dir / "PandoraLauncher/themes/caelestia.json", template)
|
||||
|
||||
|
||||
@log_exception
|
||||
def apply_spicetify(colours: dict[str, str], mode: str) -> None:
|
||||
template = gen_replace(colours, templates_dir / f"spicetify-{mode}.ini")
|
||||
@@ -191,36 +199,32 @@ def apply_htop(colours: dict[str, str]) -> None:
|
||||
def sync_papirus_colors(hex_color: str) -> None:
|
||||
"""Sync Papirus folder icon colors using hue/saturation analysis"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["which", "papirus-folders"],
|
||||
capture_output=True,
|
||||
check=False
|
||||
)
|
||||
result = subprocess.run(["which", "papirus-folders"], capture_output=True, check=False)
|
||||
if result.returncode != 0:
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
|
||||
papirus_paths = [
|
||||
Path("/usr/share/icons/Papirus"),
|
||||
Path("/usr/share/icons/Papirus-Dark"),
|
||||
Path.home() / ".local/share/icons/Papirus",
|
||||
Path.home() / ".icons/Papirus",
|
||||
]
|
||||
|
||||
|
||||
if not any(p.exists() for p in papirus_paths):
|
||||
return
|
||||
|
||||
|
||||
r = int(hex_color[0:2], 16)
|
||||
g = int(hex_color[2:4], 16)
|
||||
b = int(hex_color[4:6], 16)
|
||||
|
||||
|
||||
# Brightness and saturation
|
||||
max_val = max(r, g, b)
|
||||
min_val = min(r, g, b)
|
||||
brightness = max_val
|
||||
saturation = 0 if max_val == 0 else ((max_val - min_val) * 100) // max_val
|
||||
|
||||
|
||||
# Low saturation = grayscale
|
||||
if saturation < 20:
|
||||
if brightness < 85:
|
||||
@@ -235,13 +239,13 @@ def sync_papirus_colors(hex_color: str) -> None:
|
||||
color = _determine_hue_color(r, g, b, brightness, use_pale)
|
||||
else:
|
||||
color = _determine_hue_color(r, g, b, brightness, False)
|
||||
|
||||
|
||||
try:
|
||||
subprocess.Popen(
|
||||
["sudo", "-n", "papirus-folders", "-C", color, "-u"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
start_new_session=True
|
||||
start_new_session=True,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -253,7 +257,7 @@ def _determine_hue_color(r: int, g: int, b: int, brightness: int, use_pale: bool
|
||||
r_ratio = (r * 100) // b if b > 0 else 0
|
||||
g_ratio = (g * 100) // b if b > 0 else 0
|
||||
rg_diff = abs(r - g)
|
||||
|
||||
|
||||
if r_ratio > 70 and g_ratio > 70:
|
||||
# Both R and G high relative to B = light blue/periwinkle
|
||||
if rg_diff < 15:
|
||||
@@ -301,7 +305,7 @@ def _determine_hue_color(r: int, g: int, b: int, brightness: int, use_pale: bool
|
||||
def apply_gtk(colours: dict[str, str], mode: str) -> None:
|
||||
gtk_template = gen_replace(colours, templates_dir / "gtk.css", hash=True)
|
||||
thunar_template = gen_replace(colours, templates_dir / "thunar.css", hash=True)
|
||||
|
||||
|
||||
for gtk_version in ["gtk-3.0", "gtk-4.0"]:
|
||||
gtk_config_dir = config_dir / gtk_version
|
||||
write_file(gtk_config_dir / "gtk.css", gtk_template)
|
||||
@@ -310,35 +314,18 @@ def apply_gtk(colours: dict[str, str], mode: str) -> None:
|
||||
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/gtk-theme", "'adw-gtk3-dark'"])
|
||||
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/color-scheme", f"'prefer-{mode}'"])
|
||||
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/icon-theme", f"'Papirus-{mode.capitalize()}'"])
|
||||
|
||||
|
||||
sync_papirus_colors(colours["primary"])
|
||||
|
||||
|
||||
@log_exception
|
||||
def apply_qt(colours: dict[str, str], mode: str) -> None:
|
||||
template = gen_replace(colours, templates_dir / f"qt{mode}.colors", hash=True)
|
||||
write_file(config_dir / "qt5ct/colors/caelestia.colors", template)
|
||||
write_file(config_dir / "qt6ct/colors/caelestia.colors", template)
|
||||
colours = gen_replace(colours, templates_dir / f"qt{mode}.colors", hash=True)
|
||||
write_file(config_dir / "qtengine/caelestia.colors", colours)
|
||||
|
||||
qtct = (templates_dir / "qtct.conf").read_text()
|
||||
qtct = qtct.replace("{{ $mode }}", mode.capitalize())
|
||||
|
||||
for ver in 5, 6:
|
||||
conf = qtct.replace("{{ $config }}", str(config_dir / f"qt{ver}ct"))
|
||||
|
||||
if ver == 5:
|
||||
conf += """
|
||||
[Fonts]
|
||||
fixed="Monospace,12,-1,5,50,0,0,0,0,0"
|
||||
general="Sans Serif,12,-1,5,50,0,0,0,0,0"
|
||||
"""
|
||||
else:
|
||||
conf += """
|
||||
[Fonts]
|
||||
fixed="Monospace,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
|
||||
general="Sans Serif,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1"
|
||||
"""
|
||||
write_file(config_dir / f"qt{ver}ct/qt{ver}ct.conf", conf)
|
||||
config = (templates_dir / "qtengine.json").read_text()
|
||||
config = config.replace("{{ $mode }}", mode.capitalize())
|
||||
write_file(config_dir / "qtengine/config.json", config)
|
||||
|
||||
|
||||
@log_exception
|
||||
@@ -350,6 +337,51 @@ def apply_warp(colours: dict[str, str], mode: str) -> None:
|
||||
write_file(data_dir / "warp-terminal/themes/caelestia.yaml", template)
|
||||
|
||||
|
||||
@log_exception
|
||||
def apply_chromium(colours: dict[str, str]) -> None:
|
||||
surface_hex = colours["surface"]
|
||||
theme_color = f"#{surface_hex}"
|
||||
browsers = [
|
||||
("chromium", Path("/etc/chromium/policies/managed")),
|
||||
("brave", Path("/etc/brave/policies/managed")),
|
||||
("google-chrome-stable", Path("/etc/opt/chrome/policies/managed")),
|
||||
]
|
||||
|
||||
for cmd, policy_dir in browsers:
|
||||
if shutil.which(cmd) is None:
|
||||
continue
|
||||
if not policy_dir.is_dir():
|
||||
subprocess.run(["sudo", "-n", "mkdir", "-p", str(policy_dir)], stderr=subprocess.DEVNULL)
|
||||
if not policy_dir.is_dir():
|
||||
print(f"Unable to create {policy_dir} directory")
|
||||
continue
|
||||
|
||||
# Use tee instead of write_file cause we need sudo
|
||||
subprocess.run(
|
||||
["sudo", "-n", "tee", str(policy_dir / "caelestia.json")],
|
||||
input=json.dumps({"BrowserThemeColor": theme_color, "BrowserColorScheme": "device"}),
|
||||
text=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
subprocess.run(
|
||||
[cmd, "--refresh-platform-policy", "--no-startup-window"],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def apply_zed(colours: dict[str, str], mode: str) -> None:
|
||||
theme_path = config_dir / "zed/themes/caelestia.json"
|
||||
# Zed's file watcher does not detect changes through symlinks,
|
||||
# so resolve to a regular file before writing
|
||||
if theme_path.is_symlink():
|
||||
theme_path.unlink()
|
||||
|
||||
content = gen_replace_dynamic(colours, templates_dir / "zed.json", mode)
|
||||
write_file(theme_path, content)
|
||||
|
||||
|
||||
@log_exception
|
||||
def apply_cava(colours: dict[str, str]) -> None:
|
||||
template = gen_replace(colours, templates_dir / "cava.conf", hash=True)
|
||||
@@ -372,14 +404,14 @@ def apply_colours(colours: dict[str, str], mode: str) -> None:
|
||||
# Use file-based lock to prevent concurrent theme changes
|
||||
lock_file = c_state_dir / "theme.lock"
|
||||
c_state_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
try:
|
||||
with open(lock_file, 'w') as lock_fd:
|
||||
with open(lock_file, "w") as lock_fd:
|
||||
try:
|
||||
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError:
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
cfg = json.loads(user_config_path.read_text())["theme"]
|
||||
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
||||
@@ -396,6 +428,8 @@ def apply_colours(colours: dict[str, str], mode: str) -> None:
|
||||
apply_discord(gen_scss(colours))
|
||||
if check("enableSpicetify"):
|
||||
apply_spicetify(colours, mode)
|
||||
if check("enablePandora"):
|
||||
apply_pandora(colours, mode)
|
||||
if check("enableFuzzel"):
|
||||
apply_fuzzel(colours)
|
||||
if check("enableBtop"):
|
||||
@@ -410,10 +444,14 @@ def apply_colours(colours: dict[str, str], mode: str) -> None:
|
||||
apply_qt(colours, mode)
|
||||
if check("enableWarp"):
|
||||
apply_warp(colours, mode)
|
||||
if check("enableChromium"):
|
||||
apply_chromium(colours)
|
||||
if check("enableZed"):
|
||||
apply_zed(colours, mode)
|
||||
if check("enableCava"):
|
||||
apply_cava(colours)
|
||||
apply_user_templates(colours, mode)
|
||||
|
||||
|
||||
finally:
|
||||
try:
|
||||
lock_file.unlink()
|
||||
|
||||
@@ -2,8 +2,10 @@ import json
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from materialyoucolor.hct import Hct
|
||||
from materialyoucolor.utils.color_utils import argb_from_rgb
|
||||
@@ -11,6 +13,7 @@ from PIL import Image
|
||||
|
||||
from caelestia.utils.hypr import message
|
||||
from caelestia.utils.material import get_colours_for_image
|
||||
from caelestia.utils.colourfulness import get_variant
|
||||
from caelestia.utils.paths import (
|
||||
compute_hash,
|
||||
user_config_path,
|
||||
@@ -24,7 +27,7 @@ from caelestia.utils.theme import apply_colours
|
||||
|
||||
|
||||
def is_valid_image(path: Path) -> bool:
|
||||
return path.is_file() and path.suffix in [".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff"]
|
||||
return path.is_file() and path.suffix in [".jpg", ".jpeg", ".png", ".webp", ".tif", ".tiff", ".gif"]
|
||||
|
||||
|
||||
def check_wall(wall: Path, filter_size: tuple[int, int], threshold: float) -> bool:
|
||||
@@ -33,7 +36,7 @@ def check_wall(wall: Path, filter_size: tuple[int, int], threshold: float) -> bo
|
||||
return width >= filter_size[0] * threshold and height >= filter_size[1] * threshold
|
||||
|
||||
|
||||
def get_wallpaper() -> str:
|
||||
def get_wallpaper() -> str | None:
|
||||
try:
|
||||
return wallpaper_path_path.read_text()
|
||||
except IOError:
|
||||
@@ -41,16 +44,16 @@ def get_wallpaper() -> str:
|
||||
|
||||
|
||||
def get_wallpapers(args: Namespace) -> list[Path]:
|
||||
dir = Path(args.random)
|
||||
if not dir.is_dir():
|
||||
directory = Path(args.random)
|
||||
if not directory.is_dir():
|
||||
return []
|
||||
|
||||
walls = [f for f in dir.rglob("*") if is_valid_image(f)]
|
||||
walls = [f for f in directory.rglob("*") if is_valid_image(f)]
|
||||
|
||||
if args.no_filter:
|
||||
return walls
|
||||
|
||||
monitors = message("monitors")
|
||||
monitors = cast(list[dict[str, int]], message("monitors"))
|
||||
filter_size = min(m["width"] for m in monitors), min(m["height"] for m in monitors)
|
||||
|
||||
return [f for f in walls if check_wall(f, filter_size, args.threshold)]
|
||||
@@ -62,14 +65,14 @@ def get_thumb(wall: Path, cache: Path) -> Path:
|
||||
if not thumb.exists():
|
||||
with Image.open(wall) as img:
|
||||
img = img.convert("RGB")
|
||||
img.thumbnail((128, 128), Image.NEAREST)
|
||||
img.thumbnail((128, 128), Image.Resampling.NEAREST)
|
||||
thumb.parent.mkdir(parents=True, exist_ok=True)
|
||||
img.save(thumb, "JPEG")
|
||||
|
||||
return thumb
|
||||
|
||||
|
||||
def get_smart_opts(wall: Path, cache: Path) -> str:
|
||||
def get_smart_opts(wall: Path, cache: Path) -> dict:
|
||||
opts_cache = cache / "smart.json"
|
||||
|
||||
try:
|
||||
@@ -77,15 +80,16 @@ def get_smart_opts(wall: Path, cache: Path) -> str:
|
||||
except (IOError, json.JSONDecodeError):
|
||||
pass
|
||||
|
||||
from caelestia.utils.colourfulness import get_variant
|
||||
|
||||
opts = {}
|
||||
|
||||
with Image.open(get_thumb(wall, cache)) as img:
|
||||
opts["variant"] = get_variant(img)
|
||||
img.thumbnail((1, 1), Image.Resampling.LANCZOS)
|
||||
|
||||
# Cast the pixel to a tuple of 3 integers to safely unpack it
|
||||
pixel = cast(tuple[int, int, int], img.getpixel((0, 0)))
|
||||
hct = Hct.from_int(argb_from_rgb(*pixel))
|
||||
|
||||
img.thumbnail((1, 1), Image.LANCZOS)
|
||||
hct = Hct.from_int(argb_from_rgb(*img.getpixel((0, 0))))
|
||||
opts["mode"] = "light" if hct.tone > 60 else "dark"
|
||||
|
||||
opts_cache.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -96,9 +100,13 @@ def get_smart_opts(wall: Path, cache: Path) -> str:
|
||||
|
||||
|
||||
def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None:
|
||||
wall = Path(wall)
|
||||
scheme = get_scheme()
|
||||
cache = wallpapers_cache_dir / compute_hash(wall)
|
||||
|
||||
if wall.suffix.lower() == ".gif":
|
||||
wall = convert_gif(wall)
|
||||
|
||||
name = "dynamic"
|
||||
|
||||
if not no_smart:
|
||||
@@ -122,13 +130,34 @@ def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None:
|
||||
}
|
||||
|
||||
|
||||
def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
||||
def convert_gif(wall: Path) -> Path:
|
||||
cache = wallpapers_cache_dir / compute_hash(wall)
|
||||
output_path = cache / "first_frame.png"
|
||||
|
||||
if not output_path.exists():
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with Image.open(wall) as img:
|
||||
try:
|
||||
img.seek(0)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
img = img.convert("RGB")
|
||||
img.save(output_path, "PNG")
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def set_wallpaper(wall: Path, no_smart: bool) -> None:
|
||||
# Make path absolute
|
||||
wall = Path(wall).resolve()
|
||||
|
||||
if not is_valid_image(wall):
|
||||
raise ValueError(f'"{wall}" is not a valid image')
|
||||
|
||||
# Use gif's 1st frame for thumb only
|
||||
wall_cache = convert_gif(wall) if wall.suffix.lower() == ".gif" else wall
|
||||
|
||||
# Update files
|
||||
wallpaper_path_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
wallpaper_path_path.write_text(str(wall))
|
||||
@@ -136,10 +165,10 @@ def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
||||
wallpaper_link_path.unlink(missing_ok=True)
|
||||
wallpaper_link_path.symlink_to(wall)
|
||||
|
||||
cache = wallpapers_cache_dir / compute_hash(wall)
|
||||
cache = wallpapers_cache_dir / compute_hash(wall_cache)
|
||||
|
||||
# Generate thumbnail or get from cache
|
||||
thumb = get_thumb(wall, cache)
|
||||
thumb = get_thumb(wall_cache, cache)
|
||||
wallpaper_thumbnail_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
wallpaper_thumbnail_path.unlink(missing_ok=True)
|
||||
wallpaper_thumbnail_path.symlink_to(thumb)
|
||||
@@ -148,7 +177,7 @@ def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
||||
|
||||
# Change mode and variant based on wallpaper colour
|
||||
if scheme.name == "dynamic" and not no_smart:
|
||||
smart_opts = get_smart_opts(wall, cache)
|
||||
smart_opts = get_smart_opts(wall_cache, cache)
|
||||
scheme.mode = smart_opts["mode"]
|
||||
scheme.variant = smart_opts["variant"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user