mirror of
https://github.com/caelestia-dots/cli.git
synced 2026-06-05 23:09:27 -05:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c930bd2604 | |||
| cc155cf432 | |||
| 6e59149fbf | |||
| 8d2b737f15 | |||
| 4bcd42f482 | |||
| 51cecd481c | |||
| c9312f3928 | |||
| bfaf4fc373 | |||
| 6e711ec289 | |||
| 7899f8348f | |||
| b0d68f0a1c | |||
| b0325a1898 | |||
| a6defd2921 | |||
| 0b9e416175 | |||
| 8ce97ea3f5 | |||
| 25c473c18e | |||
| c22916fe45 | |||
| 011989e3ca | |||
| d88cc7ff79 | |||
| a550eb79ed |
Generated
+10
-10
@@ -9,11 +9,11 @@
|
|||||||
"quickshell": "quickshell"
|
"quickshell": "quickshell"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770949235,
|
"lastModified": 1772962569,
|
||||||
"narHash": "sha256-OFeud9FjaOk6xHp/9igYl/+Zw6FJDyZNrIDNi47gsG0=",
|
"narHash": "sha256-ctRw4pVgx0IYKfA2hy90Ku37pnVX2T4q57UWp+l69fs=",
|
||||||
"owner": "caelestia-dots",
|
"owner": "caelestia-dots",
|
||||||
"repo": "shell",
|
"repo": "shell",
|
||||||
"rev": "93e8880842b03e251bf59d1ba316f2393c68574f",
|
"rev": "e183599ce9e2c8d30a14631d53eb9947220c0812",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -24,11 +24,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770841267,
|
"lastModified": 1772773019,
|
||||||
"narHash": "sha256-9xejG0KoqsoKEGp2kVbXRlEYtFFcDTHjidiuX8hGO44=",
|
"narHash": "sha256-E1bxHxNKfDoQUuvriG71+f+s/NT0qWkImXsYZNFFfCs=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ec7c70d12ce2fc37cb92aff673dcdca89d187bae",
|
"rev": "aca4d95fce4914b3892661bcb80b8087293536c6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -46,11 +46,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1769593411,
|
"lastModified": 1772925576,
|
||||||
"narHash": "sha256-WW00FaBiUmQyxvSbefvgxIjwf/WmRrEGBbwMHvW/7uQ=",
|
"narHash": "sha256-mMoiXABDtkSJxCYDrkhJ/TrrJf5M46oUfIlJvv2gkZ0=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "1e4d804e7f3fa7465811030e8da2bf10d544426a",
|
"rev": "15a84097653593dd15fad59a56befc2b7bdc270d",
|
||||||
"revCount": 732,
|
"revCount": 750,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
from pathlib import Path
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -6,6 +5,7 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from caelestia.utils.notify import close_notification, notify
|
from caelestia.utils.notify import close_notification, notify
|
||||||
from caelestia.utils.paths import recording_notif_path, recording_path, recordings_dir, user_config_path
|
from caelestia.utils.paths import recording_notif_path, recording_path, recordings_dir, user_config_path
|
||||||
|
|||||||
@@ -140,9 +140,12 @@ class Command:
|
|||||||
monitor_x = monitor.get("x")
|
monitor_x = monitor.get("x")
|
||||||
monitor_y = monitor.get("y")
|
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
|
return
|
||||||
|
|
||||||
monitor_height = monitor_height / monitor_scale
|
monitor_height = monitor_height / monitor_scale
|
||||||
monitor_width = monitor_width / monitor_scale
|
monitor_width = monitor_width / monitor_scale
|
||||||
|
|
||||||
@@ -232,7 +235,7 @@ class Command:
|
|||||||
window_id = event.split(">>>")[1].split(",")[0]
|
window_id = event.split(">>>")[1].split(",")[0]
|
||||||
else:
|
else:
|
||||||
window_id = event.split(">>")[1].split(",")[0]
|
window_id = event.split(">>")[1].split(",")[0]
|
||||||
|
|
||||||
# Remove any leading > characters
|
# Remove any leading > characters
|
||||||
window_id = window_id.lstrip(">")
|
window_id = window_id.lstrip(">")
|
||||||
|
|
||||||
@@ -268,9 +271,9 @@ class Command:
|
|||||||
data = event[13:] # Remove "openwindow>>>"
|
data = event[13:] # Remove "openwindow>>>"
|
||||||
else:
|
else:
|
||||||
data = event[12:] # Remove "openwindow>>"
|
data = event[12:] # Remove "openwindow>>"
|
||||||
|
|
||||||
window_id, workspace, window_class, title = data.split(",", 3)
|
window_id, workspace, window_class, title = data.split(",", 3)
|
||||||
|
|
||||||
# Remove any leading > characters
|
# Remove any leading > characters
|
||||||
window_id = window_id.lstrip(">")
|
window_id = window_id.lstrip(">")
|
||||||
|
|
||||||
@@ -348,19 +351,19 @@ class Command:
|
|||||||
|
|
||||||
# Find all windows that match the pattern
|
# Find all windows that match the pattern
|
||||||
matching_windows = self._find_matching_windows(temp_rule)
|
matching_windows = self._find_matching_windows(temp_rule)
|
||||||
|
|
||||||
if not matching_windows:
|
if not matching_windows:
|
||||||
print(f"No windows found matching pattern '{temp_rule.name}' with match type '{temp_rule.match_type}'")
|
print(f"No windows found matching pattern '{temp_rule.name}' with match type '{temp_rule.match_type}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Found {len(matching_windows)} matching window(s)")
|
print(f"Found {len(matching_windows)} matching window(s)")
|
||||||
|
|
||||||
# Apply rule to all matching windows
|
# Apply rule to all matching windows
|
||||||
success_count = 0
|
success_count = 0
|
||||||
for window in matching_windows:
|
for window in matching_windows:
|
||||||
window_id = window["address"][2:] # Remove "0x" prefix
|
window_id = window["address"][2:] # Remove "0x" prefix
|
||||||
window_title = window.get("title", "")
|
window_title = window.get("title", "")
|
||||||
|
|
||||||
print(f"Applying rule to window 0x{window_id}: '{window_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)
|
success = self._apply_window_actions(window_id, temp_rule.width, temp_rule.height, temp_rule.actions)
|
||||||
if success:
|
if success:
|
||||||
@@ -386,7 +389,7 @@ class Command:
|
|||||||
return
|
return
|
||||||
|
|
||||||
window_id = address[2:] # Remove "0x" prefix
|
window_id = address[2:] # Remove "0x" prefix
|
||||||
|
|
||||||
print(f"Applying rule to active window 0x{window_id}: '{window_title}'")
|
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)
|
success = self._apply_window_actions(window_id, temp_rule.width, temp_rule.height, temp_rule.actions)
|
||||||
if success:
|
if success:
|
||||||
@@ -411,7 +414,7 @@ class Command:
|
|||||||
|
|
||||||
window_title = window.get("title", "")
|
window_title = window.get("title", "")
|
||||||
initial_title = window.get("initialTitle", "")
|
initial_title = window.get("initialTitle", "")
|
||||||
|
|
||||||
# Check if window matches the pattern
|
# Check if window matches the pattern
|
||||||
matches = False
|
matches = False
|
||||||
if temp_rule.match_type == "initialTitle":
|
if temp_rule.match_type == "initialTitle":
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ def log_exception(func):
|
|||||||
Used by the `apply_()` functions so that an exception, when applying
|
Used by the `apply_()` functions so that an exception, when applying
|
||||||
a theme, does not prevent the other themes from being applied.
|
a theme, does not prevent the other themes from being applied.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_message(f'Error during execution of "{func.__name__}()": {str(e)}')
|
log_message(f'Error during execution of "{func.__name__}()": {str(e)}')
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
from materialyoucolor.blend import Blend
|
from materialyoucolor.blend import Blend
|
||||||
from materialyoucolor.dynamiccolor.material_dynamic_colors import (
|
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
|
||||||
DynamicScheme,
|
|
||||||
MaterialDynamicColors,
|
|
||||||
)
|
|
||||||
from materialyoucolor.hct import Hct
|
from materialyoucolor.hct import Hct
|
||||||
from materialyoucolor.scheme.scheme_content import SchemeContent
|
from materialyoucolor.scheme.scheme_content import SchemeContent
|
||||||
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive
|
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive
|
||||||
@@ -15,6 +12,11 @@ from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
|
|||||||
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
|
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
|
||||||
from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double
|
from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double
|
||||||
|
|
||||||
|
try:
|
||||||
|
from materialyoucolor.dynamiccolor.dynamic_scheme import DynamicScheme
|
||||||
|
except ImportError:
|
||||||
|
from materialyoucolor.scheme.dynamic_scheme import DynamicScheme
|
||||||
|
|
||||||
|
|
||||||
def hex_to_hct(hex: str) -> Hct:
|
def hex_to_hct(hex: str) -> Hct:
|
||||||
return Hct.from_int(int(f"0xFF{hex}", 16))
|
return Hct.from_int(int(f"0xFF{hex}", 16))
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import fcntl
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
import shutil
|
||||||
import fcntl
|
import subprocess
|
||||||
import sys
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from caelestia.utils.colour import get_dynamic_colours
|
from caelestia.utils.colour import get_dynamic_colours
|
||||||
from caelestia.utils.logging import log_exception
|
from caelestia.utils.logging import log_exception
|
||||||
@@ -60,7 +59,7 @@ def gen_replace_dynamic(colours: dict[str, str], template: Path, mode: str) -> s
|
|||||||
colours_dyn = get_dynamic_colours(colours)
|
colours_dyn = get_dynamic_colours(colours)
|
||||||
template_content = template.read_text()
|
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)
|
template_filled = re.sub(modeField, mode, template_filled)
|
||||||
|
|
||||||
return template_filled
|
return template_filled
|
||||||
@@ -117,6 +116,7 @@ def write_file(path: Path, content: str) -> None:
|
|||||||
f.flush()
|
f.flush()
|
||||||
shutil.move(f.name, path)
|
shutil.move(f.name, path)
|
||||||
|
|
||||||
|
|
||||||
@log_exception
|
@log_exception
|
||||||
def apply_terms(sequences: str) -> None:
|
def apply_terms(sequences: str) -> None:
|
||||||
state = c_state_dir / "sequences.txt"
|
state = c_state_dir / "sequences.txt"
|
||||||
@@ -129,6 +129,7 @@ def apply_terms(sequences: str) -> None:
|
|||||||
try:
|
try:
|
||||||
# Use non-blocking write with timeout to prevent hangs
|
# Use non-blocking write with timeout to prevent hangs
|
||||||
import os
|
import os
|
||||||
|
|
||||||
fd = os.open(str(pt), os.O_WRONLY | os.O_NONBLOCK | os.O_NOCTTY)
|
fd = os.open(str(pt), os.O_WRONLY | os.O_NONBLOCK | os.O_NOCTTY)
|
||||||
try:
|
try:
|
||||||
os.write(fd, sequences.encode())
|
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)
|
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
|
@log_exception
|
||||||
def apply_spicetify(colours: dict[str, str], mode: str) -> None:
|
def apply_spicetify(colours: dict[str, str], mode: str) -> None:
|
||||||
template = gen_replace(colours, templates_dir / f"spicetify-{mode}.ini")
|
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:
|
def sync_papirus_colors(hex_color: str) -> None:
|
||||||
"""Sync Papirus folder icon colors using hue/saturation analysis"""
|
"""Sync Papirus folder icon colors using hue/saturation analysis"""
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(["which", "papirus-folders"], capture_output=True, check=False)
|
||||||
["which", "papirus-folders"],
|
|
||||||
capture_output=True,
|
|
||||||
check=False
|
|
||||||
)
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
return
|
return
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
papirus_paths = [
|
papirus_paths = [
|
||||||
Path("/usr/share/icons/Papirus"),
|
Path("/usr/share/icons/Papirus"),
|
||||||
Path("/usr/share/icons/Papirus-Dark"),
|
Path("/usr/share/icons/Papirus-Dark"),
|
||||||
Path.home() / ".local/share/icons/Papirus",
|
Path.home() / ".local/share/icons/Papirus",
|
||||||
Path.home() / ".icons/Papirus",
|
Path.home() / ".icons/Papirus",
|
||||||
]
|
]
|
||||||
|
|
||||||
if not any(p.exists() for p in papirus_paths):
|
if not any(p.exists() for p in papirus_paths):
|
||||||
return
|
return
|
||||||
|
|
||||||
r = int(hex_color[0:2], 16)
|
r = int(hex_color[0:2], 16)
|
||||||
g = int(hex_color[2:4], 16)
|
g = int(hex_color[2:4], 16)
|
||||||
b = int(hex_color[4:6], 16)
|
b = int(hex_color[4:6], 16)
|
||||||
|
|
||||||
# Brightness and saturation
|
# Brightness and saturation
|
||||||
max_val = max(r, g, b)
|
max_val = max(r, g, b)
|
||||||
min_val = min(r, g, b)
|
min_val = min(r, g, b)
|
||||||
brightness = max_val
|
brightness = max_val
|
||||||
saturation = 0 if max_val == 0 else ((max_val - min_val) * 100) // max_val
|
saturation = 0 if max_val == 0 else ((max_val - min_val) * 100) // max_val
|
||||||
|
|
||||||
# Low saturation = grayscale
|
# Low saturation = grayscale
|
||||||
if saturation < 20:
|
if saturation < 20:
|
||||||
if brightness < 85:
|
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)
|
color = _determine_hue_color(r, g, b, brightness, use_pale)
|
||||||
else:
|
else:
|
||||||
color = _determine_hue_color(r, g, b, brightness, False)
|
color = _determine_hue_color(r, g, b, brightness, False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
["sudo", "-n", "papirus-folders", "-C", color, "-u"],
|
["sudo", "-n", "papirus-folders", "-C", color, "-u"],
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
start_new_session=True
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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
|
r_ratio = (r * 100) // b if b > 0 else 0
|
||||||
g_ratio = (g * 100) // b if b > 0 else 0
|
g_ratio = (g * 100) // b if b > 0 else 0
|
||||||
rg_diff = abs(r - g)
|
rg_diff = abs(r - g)
|
||||||
|
|
||||||
if r_ratio > 70 and g_ratio > 70:
|
if r_ratio > 70 and g_ratio > 70:
|
||||||
# Both R and G high relative to B = light blue/periwinkle
|
# Both R and G high relative to B = light blue/periwinkle
|
||||||
if rg_diff < 15:
|
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:
|
def apply_gtk(colours: dict[str, str], mode: str) -> None:
|
||||||
gtk_template = gen_replace(colours, templates_dir / "gtk.css", hash=True)
|
gtk_template = gen_replace(colours, templates_dir / "gtk.css", hash=True)
|
||||||
thunar_template = gen_replace(colours, templates_dir / "thunar.css", hash=True)
|
thunar_template = gen_replace(colours, templates_dir / "thunar.css", hash=True)
|
||||||
|
|
||||||
for gtk_version in ["gtk-3.0", "gtk-4.0"]:
|
for gtk_version in ["gtk-3.0", "gtk-4.0"]:
|
||||||
gtk_config_dir = config_dir / gtk_version
|
gtk_config_dir = config_dir / gtk_version
|
||||||
write_file(gtk_config_dir / "gtk.css", gtk_template)
|
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/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/color-scheme", f"'prefer-{mode}'"])
|
||||||
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/icon-theme", f"'Papirus-{mode.capitalize()}'"])
|
subprocess.run(["dconf", "write", "/org/gnome/desktop/interface/icon-theme", f"'Papirus-{mode.capitalize()}'"])
|
||||||
|
|
||||||
sync_papirus_colors(colours["primary"])
|
sync_papirus_colors(colours["primary"])
|
||||||
|
|
||||||
|
|
||||||
@log_exception
|
@log_exception
|
||||||
def apply_qt(colours: dict[str, str], mode: str) -> None:
|
def apply_qt(colours: dict[str, str], mode: str) -> None:
|
||||||
template = gen_replace(colours, templates_dir / f"qt{mode}.colors", hash=True)
|
colours = gen_replace(colours, templates_dir / f"qt{mode}.colors", hash=True)
|
||||||
write_file(config_dir / "qt5ct/colors/caelestia.colors", template)
|
write_file(config_dir / "qtengine/caelestia.colors", colours)
|
||||||
write_file(config_dir / "qt6ct/colors/caelestia.colors", template)
|
|
||||||
|
|
||||||
qtct = (templates_dir / "qtct.conf").read_text()
|
config = (templates_dir / "qtengine.json").read_text()
|
||||||
qtct = qtct.replace("{{ $mode }}", mode.capitalize())
|
config = config.replace("{{ $mode }}", mode.capitalize())
|
||||||
|
write_file(config_dir / "qtengine/config.json", config)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@log_exception
|
@log_exception
|
||||||
@@ -372,14 +359,14 @@ def apply_colours(colours: dict[str, str], mode: str) -> None:
|
|||||||
# Use file-based lock to prevent concurrent theme changes
|
# Use file-based lock to prevent concurrent theme changes
|
||||||
lock_file = c_state_dir / "theme.lock"
|
lock_file = c_state_dir / "theme.lock"
|
||||||
c_state_dir.mkdir(parents=True, exist_ok=True)
|
c_state_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(lock_file, 'w') as lock_fd:
|
with open(lock_file, "w") as lock_fd:
|
||||||
try:
|
try:
|
||||||
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
fcntl.flock(lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||||
except BlockingIOError:
|
except BlockingIOError:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cfg = json.loads(user_config_path.read_text())["theme"]
|
cfg = json.loads(user_config_path.read_text())["theme"]
|
||||||
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
||||||
@@ -396,6 +383,8 @@ def apply_colours(colours: dict[str, str], mode: str) -> None:
|
|||||||
apply_discord(gen_scss(colours))
|
apply_discord(gen_scss(colours))
|
||||||
if check("enableSpicetify"):
|
if check("enableSpicetify"):
|
||||||
apply_spicetify(colours, mode)
|
apply_spicetify(colours, mode)
|
||||||
|
if check("enablePandora"):
|
||||||
|
apply_pandora(colours, mode)
|
||||||
if check("enableFuzzel"):
|
if check("enableFuzzel"):
|
||||||
apply_fuzzel(colours)
|
apply_fuzzel(colours)
|
||||||
if check("enableBtop"):
|
if check("enableBtop"):
|
||||||
@@ -413,7 +402,7 @@ def apply_colours(colours: dict[str, str], mode: str) -> None:
|
|||||||
if check("enableCava"):
|
if check("enableCava"):
|
||||||
apply_cava(colours)
|
apply_cava(colours)
|
||||||
apply_user_templates(colours, mode)
|
apply_user_templates(colours, mode)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
lock_file.unlink()
|
lock_file.unlink()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from caelestia.utils.theme import apply_colours
|
|||||||
|
|
||||||
|
|
||||||
def is_valid_image(path: Path) -> bool:
|
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:
|
def check_wall(wall: Path, filter_size: tuple[int, int], threshold: float) -> bool:
|
||||||
@@ -96,9 +96,13 @@ def get_smart_opts(wall: Path, cache: Path) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None:
|
def get_colours_for_wall(wall: Path | str, no_smart: bool) -> None:
|
||||||
|
wall = Path(wall)
|
||||||
scheme = get_scheme()
|
scheme = get_scheme()
|
||||||
cache = wallpapers_cache_dir / compute_hash(wall)
|
cache = wallpapers_cache_dir / compute_hash(wall)
|
||||||
|
|
||||||
|
if wall.suffix.lower() == ".gif":
|
||||||
|
wall = convert_gif(wall)
|
||||||
|
|
||||||
name = "dynamic"
|
name = "dynamic"
|
||||||
|
|
||||||
if not no_smart:
|
if not no_smart:
|
||||||
@@ -122,6 +126,24 @@ def get_colours_for_wall(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 | str, no_smart: bool) -> None:
|
def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
||||||
# Make path absolute
|
# Make path absolute
|
||||||
wall = Path(wall).resolve()
|
wall = Path(wall).resolve()
|
||||||
@@ -129,6 +151,9 @@ def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
|||||||
if not is_valid_image(wall):
|
if not is_valid_image(wall):
|
||||||
raise ValueError(f'"{wall}" is not a valid image')
|
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
|
# Update files
|
||||||
wallpaper_path_path.parent.mkdir(parents=True, exist_ok=True)
|
wallpaper_path_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
wallpaper_path_path.write_text(str(wall))
|
wallpaper_path_path.write_text(str(wall))
|
||||||
@@ -136,10 +161,10 @@ def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
|||||||
wallpaper_link_path.unlink(missing_ok=True)
|
wallpaper_link_path.unlink(missing_ok=True)
|
||||||
wallpaper_link_path.symlink_to(wall)
|
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
|
# 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.parent.mkdir(parents=True, exist_ok=True)
|
||||||
wallpaper_thumbnail_path.unlink(missing_ok=True)
|
wallpaper_thumbnail_path.unlink(missing_ok=True)
|
||||||
wallpaper_thumbnail_path.symlink_to(thumb)
|
wallpaper_thumbnail_path.symlink_to(thumb)
|
||||||
@@ -148,7 +173,7 @@ def set_wallpaper(wall: Path | str, no_smart: bool) -> None:
|
|||||||
|
|
||||||
# Change mode and variant based on wallpaper colour
|
# Change mode and variant based on wallpaper colour
|
||||||
if scheme.name == "dynamic" and not no_smart:
|
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.mode = smart_opts["mode"]
|
||||||
scheme.variant = smart_opts["variant"]
|
scheme.variant = smart_opts["variant"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user