1489 lines
51 KiB
Python
1489 lines
51 KiB
Python
#!/usr/bin/env python3
|
||
import os, sys, argparse, re
|
||
|
||
def get_app_config_dirs(app_names):
|
||
"""
|
||
Dynamically discover all config and data directories for any native or Flatpak application.
|
||
Checks ~/.config/, ~/.local/share/, ~/.var/app/* (Flatpak sandboxes), and system paths.
|
||
"""
|
||
if isinstance(app_names, str):
|
||
app_names = [app_names]
|
||
|
||
dirs = set()
|
||
home = os.path.expanduser('~')
|
||
|
||
for name in app_names:
|
||
c1 = os.path.join(home, '.config', name)
|
||
c2 = os.path.join(home, '.local', 'share', name)
|
||
if os.path.exists(c1):
|
||
dirs.add(c1)
|
||
if os.path.exists(c2):
|
||
dirs.add(c2)
|
||
|
||
var_app = os.path.join(home, '.var', 'app')
|
||
if os.path.exists(var_app):
|
||
try:
|
||
for entry in os.scandir(var_app):
|
||
if not entry.is_dir():
|
||
continue
|
||
sub_locations = [
|
||
os.path.join(entry.path, 'config'),
|
||
os.path.join(entry.path, 'data'),
|
||
os.path.join(entry.path, '.config'),
|
||
os.path.join(entry.path, '.local', 'share')
|
||
]
|
||
for sub_loc in sub_locations:
|
||
if not os.path.exists(sub_loc):
|
||
continue
|
||
for name in app_names:
|
||
target = os.path.join(sub_loc, name)
|
||
if os.path.exists(target):
|
||
dirs.add(target)
|
||
try:
|
||
for item in os.scandir(sub_loc):
|
||
if any(n.lower() in item.name.lower() for n in app_names):
|
||
dirs.add(item.path)
|
||
except Exception:
|
||
pass
|
||
except Exception:
|
||
pass
|
||
|
||
return list(dirs)
|
||
|
||
def restart_app_if_running(name, search_patterns, launch_commands):
|
||
"""
|
||
Gracefully restarts a native or Flatpak application if it is currently running.
|
||
"""
|
||
import subprocess, time
|
||
|
||
is_running = False
|
||
for pat in search_patterns:
|
||
res = subprocess.run(["pgrep", "-i", "-f", pat], capture_output=True, text=True)
|
||
if res.returncode == 0:
|
||
is_running = True
|
||
break
|
||
|
||
if not is_running:
|
||
return
|
||
|
||
# Gracefully terminate running instances
|
||
for pat in search_patterns:
|
||
subprocess.run(["pkill", "-15", "-i", "-f", pat], capture_output=True)
|
||
|
||
# Wait for process exit & lock release buffer
|
||
for _ in range(30):
|
||
still_running = False
|
||
for pat in search_patterns:
|
||
check = subprocess.run(["pgrep", "-i", "-f", pat], capture_output=True, text=True)
|
||
if check.returncode == 0:
|
||
still_running = True
|
||
break
|
||
if not still_running:
|
||
time.sleep(0.5)
|
||
break
|
||
time.sleep(0.1)
|
||
|
||
# Launch using first available working launch command (Flatpak -> Desktop launcher -> Native binary)
|
||
for cmd in launch_commands:
|
||
try:
|
||
if cmd.startswith("flatpak run"):
|
||
app_id = cmd.split()[-1]
|
||
check_fp = subprocess.run(["flatpak", "info", app_id], capture_output=True, text=True)
|
||
if check_fp.returncode == 0:
|
||
subprocess.Popen(cmd, shell=True, start_new_session=True)
|
||
return
|
||
elif cmd.startswith("gtk-launch"):
|
||
app_id = cmd.split()[-1]
|
||
res_gtk = subprocess.run(["gtk-launch", app_id], capture_output=True)
|
||
if res_gtk.returncode == 0:
|
||
return
|
||
else:
|
||
bin_name = cmd.split()[0]
|
||
res_bin = subprocess.run(f"command -v {bin_name}", capture_output=True, shell=True)
|
||
if res_bin.returncode == 0:
|
||
subprocess.Popen(cmd, shell=True, start_new_session=True)
|
||
return
|
||
except Exception:
|
||
pass
|
||
|
||
# Generic shell fallback launch
|
||
if launch_commands:
|
||
raw_cmd = " || ".join(launch_commands)
|
||
subprocess.Popen(f"sh -c '{raw_cmd} &'", shell=True, start_new_session=True)
|
||
|
||
def sync_gtk(bg, surface, current_line, fg, accent, is_dark):
|
||
css_content = f"""/* Auto-generated by Quickshell Theme Engine */
|
||
@define-color window_bg_color {bg};
|
||
@define-color window_fg_color {fg};
|
||
@define-color view_bg_color {surface};
|
||
@define-color view_fg_color {fg};
|
||
@define-color headerbar_bg_color {bg};
|
||
@define-color headerbar_fg_color {fg};
|
||
@define-color card_bg_color {surface};
|
||
@define-color card_fg_color {fg};
|
||
@define-color dialog_bg_color {bg};
|
||
@define-color dialog_fg_color {fg};
|
||
@define-color popover_bg_color {surface};
|
||
@define-color popover_fg_color {fg};
|
||
@define-color accent_color {accent};
|
||
@define-color accent_bg_color {accent};
|
||
@define-color accent_fg_color {'#ffffff' if is_dark else '#000000'};
|
||
"""
|
||
for dir_path in [os.path.expanduser('~/.config/gtk-3.0'), os.path.expanduser('~/.config/gtk-4.0')]:
|
||
os.makedirs(dir_path, exist_ok=True)
|
||
css_file = os.path.join(dir_path, 'gtk.css')
|
||
with open(css_file, 'w') as f:
|
||
f.write(css_content)
|
||
|
||
def sync_alacritty(bg, surface, current_line, fg, accent, sub_accent):
|
||
import glob, os, re
|
||
alacritty_dirs = get_app_config_dirs(['alacritty', 'Alacritty'])
|
||
for fb in [
|
||
os.path.expanduser('~/.config/alacritty'),
|
||
os.path.expanduser('~/.var/app/io.github.alacritty.Alacritty/config/alacritty'),
|
||
os.path.expanduser('~/.var/app/org.alacritty.Alacritty/config/alacritty')
|
||
]:
|
||
if fb not in alacritty_dirs:
|
||
alacritty_dirs.append(fb)
|
||
|
||
colors_block = f"""[colors.primary]
|
||
background = "{bg}"
|
||
foreground = "{fg}"
|
||
|
||
[colors.cursor]
|
||
text = "{bg}"
|
||
cursor = "{accent}"
|
||
|
||
[colors.selection]
|
||
text = "{bg}"
|
||
background = "{accent}"
|
||
|
||
[colors.normal]
|
||
black = "{bg}"
|
||
red = "#ff9580"
|
||
green = "#8aff80"
|
||
yellow = "#ffff80"
|
||
blue = "{accent}"
|
||
magenta = "{sub_accent}"
|
||
cyan = "#80ffea"
|
||
white = "{fg}"
|
||
|
||
[colors.bright]
|
||
black = "{current_line}"
|
||
red = "#ff9580"
|
||
green = "#8aff80"
|
||
yellow = "#ffff80"
|
||
blue = "{accent}"
|
||
magenta = "{sub_accent}"
|
||
cyan = "#80ffea"
|
||
white = "{fg}"
|
||
"""
|
||
|
||
for alacritty_dir in alacritty_dirs:
|
||
try:
|
||
os.makedirs(alacritty_dir, exist_ok=True)
|
||
colors_file = os.path.join(alacritty_dir, 'colors.toml')
|
||
toml_file = os.path.join(alacritty_dir, 'alacritty.toml')
|
||
|
||
with open(colors_file, 'w', encoding='utf-8') as f:
|
||
f.write(colors_block)
|
||
f.flush()
|
||
os.fsync(f.fileno())
|
||
|
||
if os.path.exists(toml_file):
|
||
with open(toml_file, 'r', encoding='utf-8') as f:
|
||
content = f.read()
|
||
|
||
lines = content.splitlines()
|
||
cleaned_lines = []
|
||
in_colors = False
|
||
for line in lines:
|
||
stripped = line.strip()
|
||
if stripped.startswith('[colors') or stripped.startswith('[[colors'):
|
||
in_colors = True
|
||
continue
|
||
elif in_colors and stripped.startswith('[') and not stripped.startswith('[colors') and not stripped.startswith('[[colors'):
|
||
in_colors = False
|
||
|
||
if not in_colors:
|
||
cleaned_lines.append(line)
|
||
|
||
content = '\n'.join(cleaned_lines)
|
||
|
||
if 'colors.toml' not in content:
|
||
import_line = 'import = ["colors.toml"]\n'
|
||
if '[general]' in content:
|
||
content = content.replace('[general]', '[general]\n' + import_line)
|
||
else:
|
||
content = '[general]\n' + import_line + '\n' + content
|
||
|
||
with open(toml_file, 'w', encoding='utf-8') as f:
|
||
f.write(content.strip() + '\n')
|
||
else:
|
||
with open(toml_file, 'w', encoding='utf-8') as f:
|
||
f.write('[general]\nimport = ["colors.toml"]\nlive_config_reload = true\n')
|
||
except Exception:
|
||
pass
|
||
except Exception:
|
||
pass
|
||
|
||
try:
|
||
os.utime(colors_file, None)
|
||
if os.path.exists(toml_file):
|
||
os.utime(toml_file, None)
|
||
except Exception:
|
||
pass
|
||
|
||
# Send live OSC 10/11 color update sequences to all active pseudo-terminals
|
||
osc_sequence = f"\033]10;{fg}\007\033]11;{bg}\007".encode('utf-8')
|
||
for pts in glob.glob('/dev/pts/*'):
|
||
try:
|
||
if os.path.isabs(pts) and os.access(pts, os.W_OK):
|
||
fd = os.open(pts, os.O_WRONLY | os.O_NOCTTY)
|
||
os.write(fd, osc_sequence)
|
||
os.close(fd)
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_discord(bg, surface, current_line, fg, accent, sub_accent, is_dark):
|
||
import json
|
||
css_content = f"""/* Auto-generated by Quickshell Theme Engine (Catppuccin Discord Architecture) */
|
||
.visual-refresh.theme-dark, .visual-refresh .theme-dark, .theme-dark, .theme-light, :root, html, body {{
|
||
--brand-500: {accent} !important;
|
||
--brand-530: {accent} !important;
|
||
--brand-560: {accent} !important;
|
||
--brand-600: {accent} !important;
|
||
--brand-630: {accent} !important;
|
||
--brand-700: {accent} !important;
|
||
--brand-experiment: {accent} !important;
|
||
--accent-color: {accent} !important;
|
||
|
||
--__header-bar-background: {surface} !important;
|
||
--text-normal: {fg} !important;
|
||
--text-default: {fg} !important;
|
||
--text-muted: {fg} !important;
|
||
--text-link: {accent} !important;
|
||
--text-brand: {accent} !important;
|
||
--text-strong: {fg} !important;
|
||
--text-subtle: {fg} !important;
|
||
--text-primary: {fg} !important;
|
||
--text-secondary: {fg} !important;
|
||
--header-primary: {fg} !important;
|
||
--header-secondary: {fg} !important;
|
||
|
||
--app-frame-background: {bg} !important;
|
||
--background-secondary-alt: {surface} !important;
|
||
--background-accent: {current_line} !important;
|
||
--background-surface-highest: {current_line} !important;
|
||
--background-surface-higher: {surface} !important;
|
||
--background-surface-high: {surface} !important;
|
||
--background-base-lowest: {bg} !important;
|
||
--background-base-lower: {surface} !important;
|
||
--background-base-low: {surface} !important;
|
||
--bg-surface-raised: {surface} !important;
|
||
--background-gradient-highest: {surface} !important;
|
||
|
||
--home-background: {bg} !important;
|
||
--chat-background: {bg} !important;
|
||
--chat-background-default: {bg} !important;
|
||
--chat-border: {bg} !important;
|
||
--chat-text-muted: {fg} !important;
|
||
|
||
--border-muted: {current_line} !important;
|
||
--border-strong: {surface} !important;
|
||
--border-normal: {bg} !important;
|
||
--border-subtle: {bg} !important;
|
||
|
||
--custom-channel-members-bg: {surface} !important;
|
||
--card-background-default: {surface} !important;
|
||
--modal-background: {surface} !important;
|
||
--modal-footer-background: {bg} !important;
|
||
|
||
--input-background-default: {bg} !important;
|
||
--input-text-default: {fg} !important;
|
||
--input-placeholder-text-default: {fg} !important;
|
||
--background-code: {surface} !important;
|
||
|
||
--channels-default: {fg} !important;
|
||
--channel-icon: {fg} !important;
|
||
--channeltextarea-background: {surface} !important;
|
||
--icon-muted: {fg} !important;
|
||
--icon-default: {fg} !important;
|
||
--icon-strong: {fg} !important;
|
||
--icon-subtle: {fg} !important;
|
||
--icon-primary: {fg} !important;
|
||
--icon-secondary: {fg} !important;
|
||
|
||
--interactive-normal: {fg} !important;
|
||
--interactive-hover: {accent} !important;
|
||
--interactive-active: {accent} !important;
|
||
--interactive-muted: {fg} !important;
|
||
--interactive-icon-default: {fg} !important;
|
||
--interactive-icon-hover: {accent} !important;
|
||
--interactive-icon-active: {accent} !important;
|
||
--interactive-text-default: {fg} !important;
|
||
--interactive-text-hover: {accent} !important;
|
||
--interactive-text-active: {accent} !important;
|
||
|
||
--user-profile-overlay-background: {surface} !important;
|
||
}}
|
||
"""
|
||
|
||
discord_dirs = get_app_config_dirs(['vesktop', 'Vencord', 'equibop', 'equicord', 'discord', 'Discord'])
|
||
for fb in [
|
||
os.path.expanduser('~/.config/vesktop'),
|
||
os.path.expanduser('~/.config/Vencord'),
|
||
os.path.expanduser('~/.config/equibop'),
|
||
os.path.expanduser('~/.config/equicord'),
|
||
os.path.expanduser('~/.config/discord'),
|
||
os.path.expanduser('~/.var/app/dev.vencord.Vesktop/config/vesktop'),
|
||
os.path.expanduser('~/.var/app/dev.vencord.Vesktop/config/Vencord'),
|
||
os.path.expanduser('~/.var/app/com.discordapp.Discord/config/discord'),
|
||
os.path.expanduser('~/.var/app/com.discordapp.Discord/config/Vencord')
|
||
]:
|
||
if fb not in discord_dirs:
|
||
discord_dirs.append(fb)
|
||
|
||
for base_dir in discord_dirs:
|
||
try:
|
||
# 1. Write quickshell-theme.css to themes/ directory
|
||
themes_dir = os.path.join(base_dir, 'themes')
|
||
os.makedirs(themes_dir, exist_ok=True)
|
||
for theme_filename in ['quickshell-theme.css', 'quickshell.theme.css', 'quickshell.css']:
|
||
with open(os.path.join(themes_dir, theme_filename), 'w', encoding='utf-8') as f:
|
||
f.write(css_content)
|
||
|
||
# 2. Write QuickCSS file
|
||
for qc_path in [
|
||
os.path.join(base_dir, 'settings', 'quickCss.css'),
|
||
os.path.join(base_dir, 'quickCss.css')
|
||
]:
|
||
os.makedirs(os.path.dirname(qc_path), exist_ok=True)
|
||
with open(qc_path, 'w', encoding='utf-8') as f:
|
||
f.write(css_content)
|
||
|
||
# 3. Enable quickshell-theme.css and QuickCSS in settings.json
|
||
for s_path in [
|
||
os.path.join(base_dir, 'settings', 'settings.json'),
|
||
os.path.join(base_dir, 'settings.json')
|
||
]:
|
||
try:
|
||
os.makedirs(os.path.dirname(s_path), exist_ok=True)
|
||
s_data = {}
|
||
if os.path.exists(s_path):
|
||
with open(s_path, 'r', encoding='utf-8') as f:
|
||
s_data = json.load(f)
|
||
|
||
enabled_themes = s_data.get('enabledThemes', [])
|
||
if not isinstance(enabled_themes, list):
|
||
enabled_themes = []
|
||
if 'quickshell-theme.css' not in enabled_themes:
|
||
enabled_themes.append('quickshell-theme.css')
|
||
|
||
s_data['enabledThemes'] = enabled_themes
|
||
s_data['useQuickCss'] = True
|
||
|
||
with open(s_path, 'w', encoding='utf-8') as f:
|
||
json.dump(s_data, f, indent=2)
|
||
except Exception:
|
||
pass
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_vscode(bg, surface, current_line, fg, accent, sub_accent, is_dark):
|
||
import json
|
||
colors_dict = {
|
||
"editor.background": bg,
|
||
"editor.foreground": fg,
|
||
"sideBar.background": surface,
|
||
"sideBar.foreground": fg,
|
||
"activityBar.background": bg,
|
||
"activityBar.foreground": accent,
|
||
"statusBar.background": bg,
|
||
"statusBar.foreground": fg,
|
||
"titleBar.activeBackground": bg,
|
||
"titleBar.activeForeground": fg,
|
||
"tab.activeBackground": surface,
|
||
"tab.activeForeground": fg,
|
||
"tab.inactiveBackground": bg,
|
||
"tab.inactiveForeground": current_line,
|
||
"panel.background": surface,
|
||
"terminal.background": bg,
|
||
"terminal.foreground": fg
|
||
}
|
||
|
||
token_colors = {
|
||
"comments": "#7970a9" if is_dark else "#8c8c8c",
|
||
"strings": "#8aff80" if is_dark else "#22863a",
|
||
"keywords": accent,
|
||
"functions": sub_accent,
|
||
"variables": fg,
|
||
"numbers": "#ffca80" if is_dark else "#b08800",
|
||
"types": sub_accent,
|
||
"textMateRules": [
|
||
{
|
||
"scope": ["keyword", "storage.type", "storage.modifier"],
|
||
"settings": {"foreground": accent}
|
||
},
|
||
{
|
||
"scope": ["entity.name.function", "support.function"],
|
||
"settings": {"foreground": sub_accent}
|
||
},
|
||
{
|
||
"scope": ["string", "string.quoted"],
|
||
"settings": {"foreground": "#8aff80" if is_dark else "#22863a"}
|
||
},
|
||
{
|
||
"scope": ["comment"],
|
||
"settings": {"foreground": "#7970a9" if is_dark else "#8c8c8c", "fontStyle": "italic"}
|
||
},
|
||
{
|
||
"scope": ["constant.numeric", "constant.language"],
|
||
"settings": {"foreground": "#ffca80" if is_dark else "#b08800"}
|
||
},
|
||
{
|
||
"scope": ["variable", "variable.other"],
|
||
"settings": {"foreground": fg}
|
||
}
|
||
]
|
||
}
|
||
|
||
for base_dir in [os.path.expanduser('~/.config/Code/User'), os.path.expanduser('~/.config/Code - OSS/User'), os.path.expanduser('~/.config/VSCodium/User')]:
|
||
if os.path.exists(os.path.dirname(base_dir)):
|
||
os.makedirs(base_dir, exist_ok=True)
|
||
settings_path = os.path.join(base_dir, 'settings.json')
|
||
data = {}
|
||
if os.path.exists(settings_path):
|
||
try:
|
||
with open(settings_path, 'r') as f:
|
||
raw = f.read()
|
||
cleaned = re.sub(r'//.*', '', raw)
|
||
data = json.loads(cleaned) if cleaned.strip() else {}
|
||
except Exception:
|
||
data = {}
|
||
|
||
data["workbench.colorCustomizations"] = colors_dict
|
||
data["editor.tokenColorCustomizations"] = token_colors
|
||
data["workbench.colorTheme"] = "Default Dark Modern" if is_dark else "Default Light Modern"
|
||
try:
|
||
with open(settings_path, 'w') as f:
|
||
json.dump(data, f, indent=4)
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_zen(bg, surface, current_line, fg, accent, sub_accent, is_dark):
|
||
import subprocess
|
||
chrome_css = f"""/* Auto-generated by Quickshell Theme Engine */
|
||
:root {{
|
||
--zen-main-browser-background: {bg} !important;
|
||
--zen-workspace-background: {bg} !important;
|
||
--zen-colors-bg: {bg} !important;
|
||
--zen-colors-bg-alt: {bg} !important;
|
||
--zen-colors-tertiary: {surface} !important;
|
||
--zen-colors-primary: {accent} !important;
|
||
--zen-colors-secondary: {sub_accent} !important;
|
||
--zen-primary-color: {accent} !important;
|
||
--zen-theme-accent: {accent} !important;
|
||
--zen-colors-input-bg: {surface} !important;
|
||
--zen-colors-text: {fg} !important;
|
||
--zen-colors-border: {bg} !important;
|
||
--zen-borders-color: {bg} !important;
|
||
--zen-border-color: {bg} !important;
|
||
--zen-sidebar-border: {bg} !important;
|
||
--zen-sidebar-bg: {bg} !important;
|
||
--zen-sidebar-background: {bg} !important;
|
||
--zen-sidebar-top-buttons-bg: {bg} !important;
|
||
--zen-element-separation: 0px !important;
|
||
--zen-main-browser-margin: 0px !important;
|
||
--zen-main-browser-padding: 0px !important;
|
||
--zen-user-border-radius: 0px !important;
|
||
|
||
--lwt-accent-color: {bg} !important;
|
||
--lwt-text-color: {fg} !important;
|
||
--lwt-sidebar-background: {bg} !important;
|
||
--lwt-sidebar-text-color: {fg} !important;
|
||
|
||
--toolbar-bgcolor: {bg} !important;
|
||
--toolbar-color: {fg} !important;
|
||
--toolbar-field-background-color: {surface} !important;
|
||
--toolbar-field-color: {fg} !important;
|
||
--toolbar-field-focus-background-color: {surface} !important;
|
||
--toolbarbutton-hover-background: {surface} !important;
|
||
|
||
--arrowpanel-background: {surface} !important;
|
||
--arrowpanel-color: {fg} !important;
|
||
--tab-selected-bgcolor: {current_line} !important;
|
||
--tab-selected-bg: {current_line} !important;
|
||
--urlbar-box-background: {surface} !important;
|
||
--urlbar-background: {surface} !important;
|
||
--chrome-content-separator-color: {bg} !important;
|
||
--toolbox-border-bottom-color: {bg} !important;
|
||
}}
|
||
|
||
*, *::before, *::after {{
|
||
border-color: {bg} !important;
|
||
box-shadow: none !important;
|
||
outline: none !important;
|
||
}}
|
||
|
||
#main-window,
|
||
#navigator-toolbox,
|
||
#nav-bar,
|
||
#PersonalToolbar,
|
||
#urlbar-background,
|
||
#sidebar-box,
|
||
#sidebar-header,
|
||
#sidebar-splitter,
|
||
.sidebar-splitter,
|
||
#TabsToolbar,
|
||
.browserStack,
|
||
#appcontent,
|
||
#tabbrowser-tabpanels,
|
||
#zen-main-app-wrapper,
|
||
#browser,
|
||
.browserContainer,
|
||
browser[type="content"],
|
||
deck,
|
||
tabbox,
|
||
vbox,
|
||
hbox,
|
||
#zen-sidebar-top-buttons,
|
||
#zen-sidebar-bottom-buttons,
|
||
#zen-sidebar-icons-wrapper,
|
||
#zen-current-workspace-indicator,
|
||
.sidebar-panel,
|
||
#zen-sidebar-box,
|
||
#zen-appcontent-navbar-container {{
|
||
background: {bg} !important;
|
||
background-color: {bg} !important;
|
||
color: {fg} !important;
|
||
border: none !important;
|
||
border-color: {bg} !important;
|
||
box-shadow: none !important;
|
||
outline: none !important;
|
||
}}
|
||
|
||
/* Active / Selected Tab Distinct Styling in Zen Browser */
|
||
.tabbrowser-tab[selected],
|
||
.tabbrowser-tab[selected] .tab-background,
|
||
.tab-background[selected],
|
||
#tabbrowser-tabs .tabbrowser-tab[selected],
|
||
tab[selected="true"] {{
|
||
background: {surface} !important;
|
||
background-color: {surface} !important;
|
||
color: {fg} !important;
|
||
border-left: none !important;
|
||
border-radius: 10px !important;
|
||
}}
|
||
|
||
/* Zen Browser Curved Active Line Accent Color Sync */
|
||
.tab-line[selected],
|
||
.tab-line[selected="true"],
|
||
.tabbrowser-tab[selected] .tab-line,
|
||
.tabbrowser-tab[selected]::before,
|
||
.tabbrowser-tab[selected] .tab-context-line {{
|
||
background: {accent} !important;
|
||
background-color: {accent} !important;
|
||
color: {accent} !important;
|
||
}}
|
||
|
||
/* Ensure Tab Inner Content, Label & Text Have Seamless Transparent Background */
|
||
.tab-content,
|
||
.tab-label-container,
|
||
.tab-label,
|
||
.tab-text,
|
||
.tab-icon-stack,
|
||
.tab-stack {{
|
||
background: transparent !important;
|
||
background-color: transparent !important;
|
||
}}
|
||
|
||
/* Hovered & Default Tab Background Rounded Corners */
|
||
.tabbrowser-tab,
|
||
.tabbrowser-tab .tab-background,
|
||
.tab-background {{
|
||
border-radius: 10px !important;
|
||
}}
|
||
|
||
/* Hovered Tab Styling in Zen Browser */
|
||
.tabbrowser-tab:hover:not([selected]),
|
||
.tabbrowser-tab:hover:not([selected]) .tab-background {{
|
||
background: {current_line} !important;
|
||
background-color: {current_line} !important;
|
||
border-radius: 10px !important;
|
||
}}
|
||
|
||
#sidebar-splitter,
|
||
.sidebar-splitter {{
|
||
width: 0px !important;
|
||
min-width: 0px !important;
|
||
background: {bg} !important;
|
||
border: none !important;
|
||
}}
|
||
|
||
/* Sleek & Elegant Floating URL Bar & Address Dropdown */
|
||
#urlbar {{
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
}}
|
||
|
||
#urlbar-background {{
|
||
background: {surface} !important;
|
||
background-color: {surface} !important;
|
||
border-radius: 12px !important;
|
||
border: 1px solid {current_line} !important;
|
||
}}
|
||
|
||
#urlbar[breakout][breakout-extend] > #urlbar-background {{
|
||
background: {surface} !important;
|
||
background-color: {surface} !important;
|
||
border-radius: 14px !important;
|
||
border: 1px solid {accent} !important;
|
||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45) !important;
|
||
}}
|
||
|
||
#urlbar-input-container {{
|
||
background: transparent !important;
|
||
border: none !important;
|
||
padding-left: 8px !important;
|
||
padding-right: 8px !important;
|
||
}}
|
||
|
||
/* URL Bar Dropdown Results Container */
|
||
.urlbarView,
|
||
.urlbarView-body-outer,
|
||
.urlbarView-body-inner,
|
||
#urlbar-results {{
|
||
background: transparent !important;
|
||
background-color: transparent !important;
|
||
border: none !important;
|
||
padding: 6px !important;
|
||
}}
|
||
|
||
/* Clean & Modern URL Result Rows */
|
||
.urlbarView-row,
|
||
.urlbarView-row-inner {{
|
||
border-radius: 8px !important;
|
||
margin-top: 2px !important;
|
||
margin-bottom: 2px !important;
|
||
padding: 6px 10px !important;
|
||
border: none !important;
|
||
background: transparent !important;
|
||
}}
|
||
|
||
.urlbarView-row[selected],
|
||
.urlbarView-row:hover,
|
||
.urlbarView-row[selected] .urlbarView-row-inner,
|
||
.urlbarView-row:hover .urlbarView-row-inner {{
|
||
background: {current_line} !important;
|
||
background-color: {current_line} !important;
|
||
}}
|
||
|
||
.urlbarView-row[selected] .urlbarView-title,
|
||
.urlbarView-row:hover .urlbarView-title {{
|
||
color: {accent} !important;
|
||
}}
|
||
|
||
/* URL Title and Link Typography Styling */
|
||
.urlbarView-title {{
|
||
color: {fg} !important;
|
||
font-weight: 500 !important;
|
||
}}
|
||
|
||
.urlbarView-secondary,
|
||
.urlbarView-url {{
|
||
color: {sub_accent} !important;
|
||
opacity: 0.85 !important;
|
||
}}
|
||
|
||
.urlbarView-action {{
|
||
color: {accent} !important;
|
||
background: Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.15) !important;
|
||
border-radius: 6px !important;
|
||
}}
|
||
"""
|
||
|
||
content_css = f"""/* Auto-generated Newtab & Content Theme */
|
||
@-moz-document url("about:home"), url("about:newtab"), url("about:blank"), url-prefix("about:") {{
|
||
body, #root, .container, .main, html {{
|
||
background: {bg} !important;
|
||
background-color: {bg} !important;
|
||
color: {fg} !important;
|
||
}}
|
||
}}
|
||
"""
|
||
pref_lines = [
|
||
'user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);\n',
|
||
'user_pref("svg.context-properties.content.enabled", true);\n',
|
||
'user_pref("browser.sessionstore.resume_from_crash", false);\n',
|
||
'user_pref("toolkit.startup.max_resumed_crashes", -1);\n'
|
||
]
|
||
|
||
zen_bases = [
|
||
os.path.expanduser('~/.config/zen'),
|
||
os.path.expanduser('~/.zen'),
|
||
os.path.expanduser('~/.var/app/app.zen_browser.zen/.zen'),
|
||
os.path.expanduser('~/.var/app/io.github.zen_browser.zen/.zen'),
|
||
os.path.expanduser('~/.var/app/org.zen_browser.zen/.zen'),
|
||
os.path.expanduser('~/.var/app/app.zen_browser.zen/config/zen'),
|
||
os.path.expanduser('~/.var/app/io.github.zen_browser.zen/config/zen')
|
||
]
|
||
for zen_base in zen_bases:
|
||
if os.path.exists(zen_base):
|
||
for profile in os.listdir(zen_base):
|
||
profile_dir = os.path.join(zen_base, profile)
|
||
if os.path.isdir(profile_dir) and ('.' in profile or 'default' in profile.lower()):
|
||
# Ensure user.js enables stylesheets and disables crash dialogs
|
||
user_js = os.path.join(profile_dir, 'user.js')
|
||
js_content = ""
|
||
if os.path.exists(user_js):
|
||
with open(user_js, 'r') as f:
|
||
js_content = f.read()
|
||
for line in pref_lines:
|
||
pref_key = line.split('"')[1]
|
||
if pref_key not in js_content:
|
||
with open(user_js, 'a') as f:
|
||
f.write("\n" + line)
|
||
|
||
# Write userChrome.css and userContent.css
|
||
chrome_dir = os.path.join(profile_dir, 'chrome')
|
||
os.makedirs(chrome_dir, exist_ok=True)
|
||
|
||
try:
|
||
with open(os.path.join(chrome_dir, 'userChrome.css'), 'w') as f:
|
||
f.write(chrome_css)
|
||
with open(os.path.join(chrome_dir, 'userContent.css'), 'w') as f:
|
||
f.write(content_css)
|
||
except Exception:
|
||
pass
|
||
|
||
restart_app_if_running(
|
||
"zen",
|
||
["zen-browser", "zen-bin", "zen_browser", "app.zen_browser.zen", "io.github.zen_browser.zen"],
|
||
[
|
||
"flatpak run app.zen_browser.zen",
|
||
"flatpak run io.github.zen_browser.zen",
|
||
"gtk-launch app.zen_browser.zen",
|
||
"gtk-launch io.github.zen_browser.zen",
|
||
"gtk-launch zen-browser",
|
||
"zen-browser",
|
||
"zen-bin",
|
||
"zen"
|
||
]
|
||
)
|
||
|
||
def hex_to_rgb(hex_str):
|
||
try:
|
||
h = hex_str.lstrip('#')
|
||
if len(h) == 3:
|
||
h = ''.join([c*2 for c in h])
|
||
r = int(h[0:2], 16)
|
||
g = int(h[2:4], 16)
|
||
b = int(h[4:6], 16)
|
||
return f"rgb({r}, {g}, {b})"
|
||
except Exception:
|
||
return hex_str
|
||
|
||
def slugify(name):
|
||
import re
|
||
s = re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
|
||
return f"quickshell-{s}" if s else "quickshell"
|
||
|
||
PREDEFINED_VARIANTS = [
|
||
{"name": "Zoey Pink", "isDark": False, "accent": "#ec4899", "bg": "#fff0f5", "surface": "#fbcfe8", "currentLine": "#f472b6", "fg": "#4a044e"},
|
||
{"name": "Zoey Night", "isDark": True, "accent": "#f472b6", "bg": "#251c2e", "surface": "#33263e", "currentLine": "#483659", "fg": "#f5e6f8"},
|
||
{"name": "Emo Zoey", "isDark": True, "accent": "#ff2a85", "bg": "#120914", "surface": "#1c0d20", "currentLine": "#32143a", "fg": "#f4d7f7"},
|
||
|
||
{"name": "Pro", "isDark": True, "accent": "#9580ff", "bg": "#22212c", "surface": "#2b2938", "currentLine": "#454158", "fg": "#f8f8f2"},
|
||
{"name": "Blade", "isDark": True, "accent": "#80ffea", "bg": "#212c2a", "surface": "#293835", "currentLine": "#415854", "fg": "#f8f8f2"},
|
||
{"name": "Buff", "isDark": True, "accent": "#ffca80", "bg": "#2a212c", "surface": "#352938", "currentLine": "#544158", "fg": "#f8f8f2"},
|
||
{"name": "Cyan", "isDark": True, "accent": "#80ffea", "bg": "#0b0d0f", "surface": "#181b1f", "currentLine": "#414d58", "fg": "#f8f8f2"},
|
||
{"name": "Lincoln", "isDark": True, "accent": "#ffff80", "bg": "#2c2a21", "surface": "#38352a", "currentLine": "#585441", "fg": "#f8f8f2"},
|
||
{"name": "Morpheus", "isDark": True, "accent": "#ff9580", "bg": "#2c2122", "surface": "#382a2b", "currentLine": "#584145", "fg": "#f8f8f2"},
|
||
{"name": "Alucard", "isDark": False, "accent": "#6272a4", "bg": "#f8f8f2", "surface": "#e6e6e6", "currentLine": "#d0d0d0", "fg": "#282a36"},
|
||
|
||
{"name": "Gruvbox Dark", "isDark": True, "accent": "#fe8019", "bg": "#282828", "surface": "#3c3836", "currentLine": "#504945", "fg": "#ebdbb2"},
|
||
{"name": "Gruvbox Material", "isDark": True, "accent": "#ea698c", "bg": "#1d2021", "surface": "#282828", "currentLine": "#3c3836", "fg": "#ddc7a1"},
|
||
{"name": "Gruvbox Light", "isDark": False, "accent": "#af3a03", "bg": "#fbf1c7", "surface": "#ebdbb2", "currentLine": "#d5c4a1", "fg": "#3c3836"},
|
||
|
||
{"name": "Rosé Pine", "isDark": True, "accent": "#ebbcba", "bg": "#191724", "surface": "#1f1d2e", "currentLine": "#26233a", "fg": "#e0def4"},
|
||
{"name": "Rosé Pine Moon", "isDark": True, "accent": "#ea9a97", "bg": "#232136", "surface": "#2a273f", "currentLine": "#393552", "fg": "#e0def4"},
|
||
{"name": "Rosé Pine Dawn", "isDark": False, "accent": "#d7827e", "bg": "#faf4ed", "surface": "#f2e9e1", "currentLine": "#e4d7d0", "fg": "#575279"},
|
||
|
||
{"name": "Catppuccin Mocha", "isDark": True, "accent": "#cba6f7", "bg": "#1e1e2e", "surface": "#313244", "currentLine": "#45475a", "fg": "#cdd6f4"},
|
||
{"name": "Catppuccin Macchiato", "isDark": True, "accent": "#f5bde6", "bg": "#24273a", "surface": "#363a4f", "currentLine": "#494d64", "fg": "#cad3f5"},
|
||
{"name": "Catppuccin Latte", "isDark": False, "accent": "#8839ef", "bg": "#eff1f5", "surface": "#e6e9ef", "currentLine": "#ccd0da", "fg": "#4c4f69"},
|
||
|
||
{"name": "Everforest Dark", "isDark": True, "accent": "#a7c080", "bg": "#2d353b", "surface": "#343f44", "currentLine": "#3d484d", "fg": "#d3c6aa"},
|
||
{"name": "Everforest Light", "isDark": False, "accent": "#8da101", "bg": "#fdf6e3", "surface": "#f4e0c5", "currentLine": "#e5d5c5", "fg": "#5c6a72"},
|
||
|
||
{"name": "Tokyo Night", "isDark": True, "accent": "#7aa2f7", "bg": "#1a1b26", "surface": "#24283b", "currentLine": "#414868", "fg": "#c0caf5"},
|
||
{"name": "Tokyo Night Storm", "isDark": True, "accent": "#7aa2f7", "bg": "#24283b", "surface": "#1f2335", "currentLine": "#414868", "fg": "#c0caf5"},
|
||
{"name": "Tokyo Night Day", "isDark": False, "accent": "#2e7de9", "bg": "#e1e2e7", "surface": "#d5d6db", "currentLine": "#c4c8da", "fg": "#3760bf"},
|
||
|
||
{"name": "Nord Dark", "isDark": True, "accent": "#88c0d0", "bg": "#2e3440", "surface": "#3b4252", "currentLine": "#434c5e", "fg": "#eceff4"},
|
||
{"name": "Nord Light", "isDark": False, "accent": "#5e81ac", "bg": "#eceff4", "surface": "#e5e9f0", "currentLine": "#d8dee9", "fg": "#2e3440"},
|
||
|
||
{"name": "Solarized Dark", "isDark": True, "accent": "#268bd2", "bg": "#002b36", "surface": "#073642", "currentLine": "#586e75", "fg": "#839496"},
|
||
{"name": "Solarized Light", "isDark": False, "accent": "#268bd2", "bg": "#fdf6e3", "surface": "#eee8d5", "currentLine": "#93a1a1", "fg": "#657b83"},
|
||
|
||
{"name": "One Dark Pro", "isDark": True, "accent": "#61afef", "bg": "#282c34", "surface": "#21252b", "currentLine": "#3e4451", "fg": "#abb2bf"},
|
||
{"name": "One Light", "isDark": False, "accent": "#4078f2", "bg": "#fafafa", "surface": "#f0f0f0", "currentLine": "#e5e5e6", "fg": "#383a42"},
|
||
|
||
{"name": "Monokai Pro", "isDark": True, "accent": "#ffd866", "bg": "#2d2a2e", "surface": "#3a3a3a", "currentLine": "#4a4a4a", "fg": "#fcfcfa"},
|
||
|
||
{"name": "Cyberpunk Neon", "isDark": True, "accent": "#ff007f", "bg": "#120e24", "surface": "#22194d", "currentLine": "#3a2a80", "fg": "#00ff9f"}
|
||
]
|
||
|
||
def make_feishin_json(bg, surface, current_line, fg, accent, is_dark, name="Quickshell", slug="quickshell"):
|
||
mode = "dark" if is_dark else "light"
|
||
extends_theme = "defaultDark" if is_dark else "defaultLight"
|
||
|
||
return {
|
||
"id": slug,
|
||
"name": name,
|
||
"version": "1.0.0",
|
||
"author": "Quickshell",
|
||
"style": mode,
|
||
"mode": mode,
|
||
"extends": extends_theme,
|
||
"colors": {
|
||
"primary": accent,
|
||
"background": bg,
|
||
"background-alternate": surface,
|
||
"surface": surface,
|
||
"surface-foreground": fg,
|
||
"foreground": fg,
|
||
"foreground-muted": current_line
|
||
}
|
||
}
|
||
|
||
def sync_feishin(bg, surface, current_line, fg, accent, sub_accent, is_dark, variant_name="Pro"):
|
||
import json, glob
|
||
active_slug = slugify(variant_name)
|
||
|
||
feishin_dirs = get_app_config_dirs(['feishin', 'Feishin'])
|
||
for fb in [
|
||
os.path.expanduser('~/.config/feishin'),
|
||
os.path.expanduser('~/.var/app/io.github.jeffvli.feishin/config/feishin'),
|
||
os.path.expanduser('~/.var/app/org.jeffvli.feishin/config/feishin'),
|
||
os.path.expanduser('~/.var/app/feishin/config/feishin')
|
||
]:
|
||
if fb not in feishin_dirs:
|
||
feishin_dirs.append(fb)
|
||
|
||
for base_dir in feishin_dirs:
|
||
try:
|
||
themes_dir = os.path.join(base_dir, 'Themes')
|
||
os.makedirs(themes_dir, exist_ok=True)
|
||
|
||
# 1. Clean up any previous .backup files and restore as .json
|
||
for backup_file in glob.glob(os.path.join(themes_dir, '*.backup')):
|
||
try:
|
||
clean_json = backup_file[:-7]
|
||
if not os.path.exists(clean_json):
|
||
os.rename(backup_file, clean_json)
|
||
else:
|
||
os.remove(backup_file)
|
||
except Exception:
|
||
pass
|
||
|
||
# 2. Pre-generate JSON files for all predefined variants so all themes are available
|
||
for v in PREDEFINED_VARIANTS:
|
||
v_slug = slugify(v["name"])
|
||
v_json = make_feishin_json(v["bg"], v["surface"], v["currentLine"], v["fg"], v["accent"], v["isDark"], v["name"], v_slug)
|
||
v_path = os.path.join(themes_dir, f"{v_slug}.json")
|
||
with open(v_path, 'w', encoding='utf-8') as f:
|
||
json.dump(v_json, f, indent=2)
|
||
|
||
# 3. Write active variant JSON with runtime parameters + quickshell.json
|
||
active_json = make_feishin_json(bg, surface, current_line, fg, accent, is_dark, variant_name, active_slug)
|
||
active_path = os.path.join(themes_dir, f"{active_slug}.json")
|
||
with open(active_path, 'w', encoding='utf-8') as f:
|
||
json.dump(active_json, f, indent=2)
|
||
|
||
with open(os.path.join(themes_dir, "quickshell.json"), 'w', encoding='utf-8') as f:
|
||
json.dump(active_json, f, indent=2)
|
||
|
||
# 4. Auto-update preferences.json in Feishin config directory if present
|
||
pref_file = os.path.join(base_dir, 'preferences.json')
|
||
if os.path.exists(pref_file):
|
||
try:
|
||
with open(pref_file, 'r', encoding='utf-8') as f:
|
||
pref_data = json.load(f)
|
||
pref_data["theme"] = active_slug
|
||
with open(pref_file, 'w', encoding='utf-8') as f:
|
||
json.dump(pref_data, f, indent=2)
|
||
except Exception:
|
||
pass
|
||
except Exception:
|
||
pass
|
||
|
||
restart_app_if_running(
|
||
"feishin",
|
||
["feishin", "io.github.jeffvli.feishin", "org.jeffvli.feishin"],
|
||
[
|
||
"flatpak run io.github.jeffvli.feishin",
|
||
"flatpak run org.jeffvli.feishin",
|
||
"gtk-launch io.github.jeffvli.feishin",
|
||
"gtk-launch feishin",
|
||
"feishin"
|
||
]
|
||
)
|
||
|
||
def sync_starship(bg, surface, current_line, fg, accent, sub_accent, is_dark):
|
||
starship_dir = os.path.expanduser('~/.config')
|
||
os.makedirs(starship_dir, exist_ok=True)
|
||
starship_file = os.path.join(starship_dir, 'starship.toml')
|
||
|
||
red_color = "#ff5555" if is_dark else "#b91c1c"
|
||
green_color = "#50fa7b" if is_dark else "#15803d"
|
||
yellow_color = "#f1fa8c" if is_dark else "#b45309"
|
||
blue_color = accent
|
||
cyan_color = "#8be9fd" if is_dark else "#0284c7"
|
||
|
||
starship_config = f"""# Starship Minimal & Efficient Theme with Nerd Fonts (Auto-synced with Quickshell)
|
||
format = \"\"\"
|
||
$os\\
|
||
$directory\\
|
||
$git_branch\\
|
||
$git_status\\
|
||
$package\\
|
||
$c\\
|
||
$cpp\\
|
||
$cmake\\
|
||
$golang\\
|
||
$java\\
|
||
$nodejs\\
|
||
$python\\
|
||
$rust\\
|
||
$cmd_duration\\
|
||
$status\\
|
||
$line_break\\
|
||
$character\"\"\"
|
||
|
||
palette = "quickshell"
|
||
|
||
[os]
|
||
disabled = false
|
||
style = "accent"
|
||
format = "[$symbol]($style) "
|
||
|
||
[os.symbols]
|
||
Arch = ""
|
||
EndeavourOS = ""
|
||
NixOS = ""
|
||
Fedora = ""
|
||
Ubuntu = ""
|
||
Debian = ""
|
||
Manjaro = ""
|
||
Pop = ""
|
||
Linux = ""
|
||
|
||
[directory]
|
||
style = "bold accent"
|
||
format = "[$path]($style)[$read_only]($read_only_style) "
|
||
truncation_length = 3
|
||
truncation_symbol = "…/"
|
||
home_symbol = "~"
|
||
read_only = " 🔒"
|
||
read_only_style = "red"
|
||
|
||
[git_branch]
|
||
symbol = " "
|
||
style = "bold sub_accent"
|
||
format = "[$symbol$branch]($style) "
|
||
|
||
[git_status]
|
||
style = "red"
|
||
format = "([$all_status$ahead_behind]($style) )"
|
||
conflicted = "="
|
||
ahead = "⇡${{count}}"
|
||
behind = "⇣${{count}}"
|
||
diverged = "⇕⇡${{ahead_count}}⇣${{behind_count}}"
|
||
untracked = "?${{count}}"
|
||
stashed = "$${{count}}"
|
||
modified = "!${{count}}"
|
||
staged = "+${{count}}"
|
||
renamed = "»${{count}}"
|
||
deleted = "-${{count}}"
|
||
|
||
[cmd_duration]
|
||
min_time = 2000
|
||
style = "yellow"
|
||
format = "[⏱ $duration]($style) "
|
||
|
||
[status]
|
||
disabled = false
|
||
style = "bold red"
|
||
symbol = "✖ "
|
||
format = "[$symbol$status]($style) "
|
||
|
||
[character]
|
||
success_symbol = "[❯](accent)"
|
||
error_symbol = "[❯](red)"
|
||
vimcmd_symbol = "[❮](sub_accent)"
|
||
|
||
[package]
|
||
disabled = true
|
||
|
||
[nodejs]
|
||
symbol = " "
|
||
style = "green"
|
||
format = "[$symbol($version )]($style)"
|
||
|
||
[python]
|
||
symbol = " "
|
||
style = "yellow"
|
||
format = "[$symbol($version )]($style)"
|
||
|
||
[rust]
|
||
symbol = " "
|
||
style = "red"
|
||
format = "[$symbol($version )]($style)"
|
||
|
||
[golang]
|
||
symbol = " "
|
||
style = "cyan"
|
||
format = "[$symbol($version )]($style)"
|
||
|
||
[c]
|
||
symbol = " "
|
||
style = "blue"
|
||
format = "[$symbol($version )]($style)"
|
||
|
||
[palettes.quickshell]
|
||
accent = "{accent}"
|
||
sub_accent = "{sub_accent}"
|
||
bg = "{bg}"
|
||
surface = "{surface}"
|
||
fg = "{fg}"
|
||
current_line = "{current_line}"
|
||
red = "{red_color}"
|
||
green = "{green_color}"
|
||
yellow = "{yellow_color}"
|
||
blue = "{blue_color}"
|
||
cyan = "{cyan_color}"
|
||
"""
|
||
try:
|
||
with open(starship_file, 'w', encoding='utf-8') as f:
|
||
f.write(starship_config)
|
||
except Exception as e:
|
||
print(f"[Starship Sync Error] {e}", flush=True)
|
||
|
||
def sync_vim(bg, surface, current_line, fg, accent, sub_accent, is_dark):
|
||
mode = "dark" if is_dark else "light"
|
||
comment_color = "#7970a9" if is_dark else "#8c8c8c"
|
||
green_color = "#8aff80" if is_dark else "#16a34a"
|
||
orange_color = "#ffca80" if is_dark else "#ea580c"
|
||
red_color = "#ff9580" if is_dark else "#dc2626"
|
||
|
||
vim_theme_content = f"""" Auto-generated by Quickshell Theme Engine
|
||
highlight clear
|
||
if exists("syntax_on")
|
||
syntax reset
|
||
endif
|
||
let g:colors_name = "quickshell_theme"
|
||
|
||
set background={mode}
|
||
|
||
hi Normal guifg={fg} guibg={bg}
|
||
hi Terminal guifg={fg} guibg={bg}
|
||
hi CursorLine guibg={surface}
|
||
hi CursorColumn guibg={surface}
|
||
hi LineNr guifg={current_line} guibg={bg}
|
||
hi CursorLineNr guifg={accent} guibg={surface} gui=bold
|
||
hi Visual guibg={current_line}
|
||
hi StatusLine guifg={fg} guibg={surface} gui=bold
|
||
hi StatusLineNC guifg={current_line} guibg={bg}
|
||
hi VertSplit guifg={current_line} guibg={bg}
|
||
hi Pmenu guifg={fg} guibg={surface}
|
||
hi PmenuSel guifg={bg} guibg={accent}
|
||
hi Search guifg={bg} guibg={accent}
|
||
hi IncSearch guifg={bg} guibg={sub_accent}
|
||
|
||
hi Comment guifg={comment_color} gui=italic
|
||
hi Constant guifg={sub_accent}
|
||
hi String guifg={green_color}
|
||
hi Character guifg={green_color}
|
||
hi Number guifg={orange_color}
|
||
hi Boolean guifg={sub_accent}
|
||
hi Float guifg={orange_color}
|
||
hi Identifier guifg={fg}
|
||
hi Function guifg={accent} gui=bold
|
||
hi Statement guifg={accent}
|
||
hi Conditional guifg={accent}
|
||
hi Repeat guifg={accent}
|
||
hi Label guifg={accent}
|
||
hi Operator guifg={sub_accent}
|
||
hi Keyword guifg={accent} gui=bold
|
||
hi Exception guifg={accent}
|
||
hi PreProc guifg={sub_accent}
|
||
hi Include guifg={accent}
|
||
hi Define guifg={accent}
|
||
hi Macro guifg={sub_accent}
|
||
hi PreCondit guifg={accent}
|
||
hi Type guifg={sub_accent}
|
||
hi StorageClass guifg={accent}
|
||
hi Structure guifg={accent}
|
||
hi Typedef guifg={accent}
|
||
hi Special guifg={sub_accent}
|
||
hi SpecialChar guifg={sub_accent}
|
||
hi Tag guifg={accent}
|
||
hi Delimiter guifg={fg}
|
||
hi SpecialComment guifg={comment_color} gui=bold
|
||
hi Debug guifg={sub_accent}
|
||
hi Underlined guifg={accent} gui=underline
|
||
hi Error guifg={fg} guibg={red_color}
|
||
hi Todo guifg={bg} guibg={accent} gui=bold
|
||
"""
|
||
|
||
colors_dir = os.path.expanduser('~/.vim/colors')
|
||
os.makedirs(colors_dir, exist_ok=True)
|
||
with open(os.path.join(colors_dir, 'quickshell_theme.vim'), 'w') as f:
|
||
f.write(vim_theme_content)
|
||
|
||
vimrc_path = os.path.expanduser('~/.vimrc')
|
||
vimrc_content = f"""syntax on
|
||
set termguicolors
|
||
set background={mode}
|
||
colorscheme quickshell_theme
|
||
"""
|
||
with open(vimrc_path, 'w') as f:
|
||
f.write(vimrc_content)
|
||
|
||
def sync_btop(bg, surface, current_line, fg, accent, sub_accent):
|
||
btop_dir = os.path.expanduser('~/.config/btop')
|
||
themes_dir = os.path.join(btop_dir, 'themes')
|
||
os.makedirs(themes_dir, exist_ok=True)
|
||
|
||
theme_content = f"""# Auto-generated by Quickshell Theme Engine
|
||
theme[main_bg]="{bg}"
|
||
theme[main_fg]="{fg}"
|
||
theme[title]="{fg}"
|
||
theme[hi_fg]="{accent}"
|
||
theme[selected_bg]="{surface}"
|
||
theme[selected_fg]="{accent}"
|
||
theme[inactive_fg]="{current_line}"
|
||
theme[graph_text]="{fg}"
|
||
theme[meter_bg]="{surface}"
|
||
theme[proc_misc]="{sub_accent}"
|
||
theme[cpu_box]="{accent}"
|
||
theme[mem_box]="{sub_accent}"
|
||
theme[net_box]="{accent}"
|
||
theme[proc_box]="{sub_accent}"
|
||
theme[div_line]="{current_line}"
|
||
theme[temp_start]="{accent}"
|
||
theme[temp_mid]="{sub_accent}"
|
||
theme[temp_end]="#ff9580"
|
||
theme[cpu_start]="{accent}"
|
||
theme[cpu_mid]="{sub_accent}"
|
||
theme[cpu_end]="#ff9580"
|
||
theme[free_start]="{accent}"
|
||
theme[free_mid]="{sub_accent}"
|
||
theme[free_end]="#8aff80"
|
||
theme[cached_start]="{accent}"
|
||
theme[cached_mid]="{sub_accent}"
|
||
theme[cached_end]="#ffff80"
|
||
theme[available_start]="{accent}"
|
||
theme[available_mid]="{sub_accent}"
|
||
theme[available_end]="#80ffea"
|
||
theme[used_start]="{accent}"
|
||
theme[used_mid]="{sub_accent}"
|
||
theme[used_end]="#ff9580"
|
||
theme[download_start]="{accent}"
|
||
theme[download_mid]="{sub_accent}"
|
||
theme[download_end]="#80ffea"
|
||
theme[upload_start]="{accent}"
|
||
theme[upload_mid]="{sub_accent}"
|
||
theme[upload_end]="#ff80bf"
|
||
"""
|
||
try:
|
||
with open(os.path.join(themes_dir, 'quickshell.theme'), 'w') as f:
|
||
f.write(theme_content)
|
||
except Exception:
|
||
pass
|
||
|
||
conf_file = os.path.join(btop_dir, 'btop.conf')
|
||
if os.path.exists(conf_file):
|
||
try:
|
||
with open(conf_file, 'r') as f:
|
||
conf = f.read()
|
||
if 'color_theme =' in conf:
|
||
lines = conf.splitlines()
|
||
new_lines = []
|
||
for l in lines:
|
||
if l.strip().startswith('color_theme ='):
|
||
new_lines.append('color_theme = "quickshell"')
|
||
else:
|
||
new_lines.append(l)
|
||
conf = '\n'.join(new_lines) + '\n'
|
||
else:
|
||
conf = 'color_theme = "quickshell"\n' + conf
|
||
with open(conf_file, 'w') as f:
|
||
f.write(conf)
|
||
except Exception:
|
||
pass
|
||
else:
|
||
try:
|
||
with open(conf_file, 'w') as f:
|
||
f.write('color_theme = "quickshell"\n')
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_fastfetch(bg, surface, current_line, fg, accent, sub_accent):
|
||
# Preserves user's exact master fastfetch configuration from ~/.config/fastfetch/
|
||
pass
|
||
|
||
def sync_ghostty(bg, surface, current_line, fg, accent, sub_accent):
|
||
ghostty_dir = os.path.expanduser('~/.config/ghostty')
|
||
os.makedirs(ghostty_dir, exist_ok=True)
|
||
conf_path = os.path.join(ghostty_dir, 'config')
|
||
|
||
config_content = f"""# Auto-generated by Quickshell Theme Engine
|
||
background = {bg}
|
||
foreground = {fg}
|
||
selection-background = {current_line}
|
||
selection-foreground = {fg}
|
||
cursor-color = {accent}
|
||
palette = 0={bg}
|
||
palette = 1=#ff9580
|
||
palette = 2=#8aff80
|
||
palette = 3=#ffff80
|
||
palette = 4={accent}
|
||
palette = 5={sub_accent}
|
||
palette = 6=#80ffea
|
||
palette = 7={fg}
|
||
palette = 8={current_line}
|
||
palette = 9=#ff9580
|
||
palette = 10=#8aff80
|
||
palette = 11=#ffff80
|
||
palette = 12={accent}
|
||
palette = 13={sub_accent}
|
||
palette = 14=#80ffea
|
||
palette = 15=#ffffff
|
||
"""
|
||
try:
|
||
with open(conf_path, 'w') as f:
|
||
f.write(config_content)
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_micro(bg, surface, current_line, fg, accent, sub_accent):
|
||
micro_dir = os.path.expanduser('~/.config/micro')
|
||
colors_dir = os.path.join(micro_dir, 'colorschemes')
|
||
os.makedirs(colors_dir, exist_ok=True)
|
||
|
||
colorscheme = f"""color-link default "{fg},{bg}"
|
||
color-link comment "#7970a9"
|
||
color-link identifier "{sub_accent}"
|
||
color-link constant "{sub_accent}"
|
||
color-link statement "{accent}"
|
||
color-link symbol "{sub_accent}"
|
||
color-link preproc "{sub_accent}"
|
||
color-link type "{accent}"
|
||
color-link special "{sub_accent}"
|
||
color-link ignore "default"
|
||
color-link error "bold #ff9580"
|
||
color-link todo "bold #ffff80"
|
||
color-link indent-char "{current_line}"
|
||
color-link line-number "{current_line}"
|
||
color-link current-line-number "{accent}"
|
||
color-link statusline "{fg},{surface}"
|
||
color-link tabbar "{fg},{bg}"
|
||
color-link cursor-line "{surface}"
|
||
color-link color-column "{surface}"
|
||
color-link divider "{current_line}"
|
||
"""
|
||
try:
|
||
with open(os.path.join(colors_dir, 'quickshell.micro'), 'w') as f:
|
||
f.write(colorscheme)
|
||
except Exception:
|
||
pass
|
||
|
||
settings_file = os.path.join(micro_dir, 'settings.json')
|
||
if os.path.exists(settings_file):
|
||
try:
|
||
import json
|
||
with open(settings_file, 'r') as f:
|
||
data = json.load(f)
|
||
data["colorscheme"] = "quickshell"
|
||
with open(settings_file, 'w') as f:
|
||
json.dump(data, f, indent=4)
|
||
except Exception:
|
||
pass
|
||
else:
|
||
try:
|
||
import json
|
||
with open(settings_file, 'w') as f:
|
||
json.dump({"colorscheme": "quickshell"}, f, indent=4)
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_konsole(bg, surface, current_line, fg, accent, sub_accent):
|
||
konsole_dir = os.path.expanduser('~/.local/share/konsole')
|
||
os.makedirs(konsole_dir, exist_ok=True)
|
||
|
||
colorscheme = f"""[Background]
|
||
Color={bg}
|
||
|
||
[BackgroundFaint]
|
||
Color={surface}
|
||
|
||
[BackgroundIntense]
|
||
Color={surface}
|
||
|
||
[Color0]
|
||
Color={bg}
|
||
|
||
[Color0Faint]
|
||
Color={bg}
|
||
|
||
[Color0Intense]
|
||
Color={current_line}
|
||
|
||
[Color1]
|
||
Color=#ff9580
|
||
|
||
[Color1Faint]
|
||
Color=#ff9580
|
||
|
||
[Color1Intense]
|
||
Color=#ff9580
|
||
|
||
[Color2]
|
||
Color=#8aff80
|
||
|
||
[Color2Faint]
|
||
Color=#8aff80
|
||
|
||
[Color2Intense]
|
||
Color=#8aff80
|
||
|
||
[Color3]
|
||
Color=#ffff80
|
||
|
||
[Color3Faint]
|
||
Color=#ffff80
|
||
|
||
[Color3Intense]
|
||
Color=#ffff80
|
||
|
||
[Color4]
|
||
Color={accent}
|
||
|
||
[Color4Faint]
|
||
Color={accent}
|
||
|
||
[Color4Intense]
|
||
Color={accent}
|
||
|
||
[Color5]
|
||
Color={sub_accent}
|
||
|
||
[Color5Faint]
|
||
Color={sub_accent}
|
||
|
||
[Color5Intense]
|
||
Color={sub_accent}
|
||
|
||
[Color6]
|
||
Color=#80ffea
|
||
|
||
[Color6Faint]
|
||
Color=#80ffea
|
||
|
||
[Color6Intense]
|
||
Color=#80ffea
|
||
|
||
[Color7]
|
||
Color={fg}
|
||
|
||
[Color7Faint]
|
||
Color={fg}
|
||
|
||
[Color7Intense]
|
||
Color=#ffffff
|
||
|
||
[Foreground]
|
||
Color={fg}
|
||
|
||
[ForegroundFaint]
|
||
Color={fg}
|
||
|
||
[ForegroundIntense]
|
||
Color=#ffffff
|
||
|
||
[General]
|
||
Description=Quickshell Dynamic Theme
|
||
Opacity=1
|
||
Wallpaper=
|
||
"""
|
||
try:
|
||
with open(os.path.join(konsole_dir, 'Quickshell.colorscheme'), 'w') as f:
|
||
f.write(colorscheme)
|
||
except Exception:
|
||
pass
|
||
|
||
def sync_xresources(bg, surface, current_line, fg, accent, sub_accent):
|
||
xres_path = os.path.expanduser('~/.Xresources')
|
||
xres_content = f"""! Auto-generated by Quickshell Theme Engine
|
||
*.background: {bg}
|
||
*.foreground: {fg}
|
||
*.cursorColor: {accent}
|
||
*.color0: {bg}
|
||
*.color1: #ff9580
|
||
*.color2: #8aff80
|
||
*.color3: #ffff80
|
||
*.color4: {accent}
|
||
*.color5: {sub_accent}
|
||
*.color6: #80ffea
|
||
*.color7: {fg}
|
||
*.color8: {current_line}
|
||
*.color9: #ff9580
|
||
*.color10: #8aff80
|
||
*.color11: #ffff80
|
||
*.color12: {accent}
|
||
*.color13: {sub_accent}
|
||
*.color14: #80ffea
|
||
*.color15: #ffffff
|
||
"""
|
||
try:
|
||
with open(xres_path, 'w') as f:
|
||
f.write(xres_content)
|
||
subprocess.run(["xrdb", "-merge", xres_path], check=False, stderr=subprocess.DEVNULL)
|
||
except Exception:
|
||
pass
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser()
|
||
parser.add_argument('--bg', required=True)
|
||
parser.add_argument('--surface', required=True)
|
||
parser.add_argument('--currentLine', required=True)
|
||
parser.add_argument('--fg', required=True)
|
||
parser.add_argument('--accent', required=True)
|
||
parser.add_argument('--subAccent', required=True)
|
||
parser.add_argument('--isDark', required=True)
|
||
parser.add_argument('--variantName', default='Pro')
|
||
args = parser.parse_args()
|
||
|
||
is_dark = args.isDark.lower() == 'true'
|
||
sync_gtk(args.bg, args.surface, args.currentLine, args.fg, args.accent, is_dark)
|
||
sync_alacritty(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
sync_discord(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent, is_dark)
|
||
sync_vscode(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent, is_dark)
|
||
sync_zen(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent, is_dark)
|
||
sync_feishin(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent, is_dark, args.variantName)
|
||
sync_starship(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent, is_dark)
|
||
sync_vim(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent, is_dark)
|
||
sync_btop(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
sync_fastfetch(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
sync_ghostty(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
sync_micro(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
sync_konsole(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
sync_xresources(args.bg, args.surface, args.currentLine, args.fg, args.accent, args.subAccent)
|
||
|
||
if __name__ == '__main__':
|
||
main()
|
||
|