idk what i update

This commit is contained in:
sankalp committed 2025-02-28 16:22:38 +05:45
1 parent 3e0fe4c1cd
commit b39de75865
11 files changed
+425 -157

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"prefix_restart": "SHIFT ALT", "suffix_restart": "T", "prefix_bluetooth": "ALT", "suffix_bluetooth": "B", "prefix_emoji": "SUPER", "suffix_emoji": "E", "prefix_cliphist": "SUPER", "suffix_cliphist": "V", "prefix_todo": "SUPER", "suffix_todo": "T", "prefix_sh": "SUPER", "suffix_sh": "S", "prefix_walls": "SUPER", "suffix_walls": "W", "prefix_screenshotregion": "SUPER", "suffix_screenshotregion": "Z", "prefix_screenshot": "", "suffix_screenshot": "Print", "prefix_screencast": "SUPER", "suffix_screencast": "R", "prefix_launcher": "SUPER", "suffix_launcher": "D", "prefix_power": "SUPER", "suffix_power": "X", "prefix_toggle": "SUPER", "suffix_toggle": "H", "prefix_randomwallpaper": "ALT SHIFT", "suffix_randomwallpaper": "W", "wallpapers_dir": "/home/sankalp/Pictures/wallpapers"}
{"prefix_restart": "SHIFT ALT", "suffix_restart": "T", "prefix_bluetooth": "ALT", "suffix_bluetooth": "B", "prefix_emoji": "SUPER", "suffix_emoji": "E", "prefix_cliphist": "SUPER", "suffix_cliphist": "V", "prefix_todo": "SUPER", "suffix_todo": "T", "prefix_sh": "SUPER", "suffix_sh": "S", "prefix_walls": "SUPER", "suffix_walls": "W", "prefix_screenshotregion": "SUPER", "suffix_screenshotregion": "Z", "prefix_screenshot": "", "suffix_screenshot": "Print", "prefix_screencast": "SUPER", "suffix_screencast": "R", "prefix_launcher": "SUPER", "suffix_launcher": "D", "prefix_power": "SUPER", "suffix_power": "X", "prefix_wifi": "SUPER", "suffix_wifi": "N", "prefix_toggle": "SUPER", "suffix_toggle": "H", "prefix_randomwallpaper": "ALT SHIFT", "suffix_randomwallpaper": "W", "wallpapers_dir": "/home/sankalp/Pictures/wallpapers"}
@@ -9,5 +9,16 @@
":pw": "launcher.open(\"power\")",
":nw": "launcher.open(\"wifi\")",
":cl": "launcher.open(\"calendar\")"
}
},
"pinned_apps": [
"kitty",
"code",
"materialgram",
"vesktop",
"spotify",
"libreoffice",
"wasistlos",
"pavucontrol",
"zen-browser"
]
}
+1 -1
View File
@@ -2,4 +2,4 @@
"color-scheme": "dark",
"custom-color": "none",
"generation-scheme": "tonalSpot"
}
}
+2 -2
View File
@@ -117,7 +117,7 @@ def copy_wallpapers():
subprocess.run(
[
"python",
os.path.expanduser("~/Modus/config/wallpaper.py"),
os.path.expanduser("~/Modus/config/scripts/wallpaper.py"),
"-I",
os.path.join(dest_wallpaper_dir, "example-1.jpg"),
]
@@ -150,7 +150,7 @@ exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
$fabricSend = fabric-cli exec modus
$scriptsDir = $HOME/Modus/config
$scriptsDir = $HOME/Modus/config/scripts/
bind = {bind_vars["prefix_restart"]}, {bind_vars["suffix_restart"]}, exec, killall modus; python {home}/Modus/main.py # Reload Modus | Default: SUPER ALT + T
bind = {bind_vars["prefix_bluetooth"]}, {bind_vars["suffix_bluetooth"]}, exec, $fabricSend 'launcher.open("bluetooth")' # Bluetooth | Default: ALT + B
+3 -2
View File
@@ -5,10 +5,11 @@ exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
$fabricSend = fabric-cli exec modus
$scriptsDir = $HOME/Modus/config
$scriptsDir = $HOME/Modus/config/scripts/
bind = SHIFT ALT, T, exec, killall modus; python /home/sankalp/Modus/main.py # Reload Modus | Default: SUPER ALT + T
bind = ALT, B, exec, $fabricSend 'launcher.open("bluetooth")' # Bluetooth | Default: ALT + B
bind = SUPER, N, exec, $fabricSend 'launcher.open("wifi")' # Wifi | Default: SUPER + N
bind = SUPER, E, exec, $fabricSend 'launcher.open("emoji")' # Emoji | Default: SUPER + E
bind = SUPER, V, exec, $fabricSend 'launcher.open("cliphist")' # Cliphist | Default: SUPER + V
bind = SUPER, T, exec, $fabricSend 'launcher.open("todo")' # Todo | Default: SUPER + T
@@ -20,7 +21,7 @@ bind = SUPER, X, exec, $fabricSend 'launcher.open("power")' # Power Menu | Defau
bind = SUPER, H, exec, $fabricSend 'bar.toggle_hidden()' # Toggle Bar | Default: SUPER + H
bind = SUPER, Z, exec, $fabricSend 'sc.screenshot()' # Screenshot Region | Default: SUPER + Z
bind = , Print, exec, $fabricSend 'sc.screenshot(True)' # ScreenshotFullScreen | Default: Print
bind = SUPER, R, exec, $fabricSend 'sc.screencast()' # Screencast | Default: SUPER + R
bind = SUPER, R, exec, $fabricSend 'sc.screencast_start()' # Screencast | Default: SUPER + R
# Wallpapers directory: /home/sankalp/Pictures/wallpapers
+119 -142
View File
@@ -7,23 +7,22 @@ import argparse
from materialyoucolor.quantize import QuantizeCelebi # type: ignore
from materialyoucolor.score.score import Score # type: ignore
from materialyoucolor.hct import Hct # type: ignore
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors # type: ignore # noqa
from materialyoucolor.scheme.dynamic_scheme import DynamicScheme # type: ignore # noqa
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot # type: ignore # noqa
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive # type: ignore # noqa
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad # type: ignore # noqa
from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome # type: ignore # noqa
from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow # type: ignore # noqa
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant # type: ignore # noqa
from materialyoucolor.scheme.scheme_neutral import SchemeNeutral # type: ignore # noqa
from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity # type: ignore # noqa
from materialyoucolor.scheme.scheme_content import SchemeContent # type: ignore # noqa
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors # type: ignore # noqa
from materialyoucolor.scheme.dynamic_scheme import DynamicScheme # type: ignore # noqa
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot # type: ignore # noqa
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive # type: ignore # noqa
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad # type: ignore # noqa
from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome # type: ignore # noqa
from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow # type: ignore # noqa
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant # type: ignore # noqa
from materialyoucolor.scheme.scheme_neutral import SchemeNeutral # type: ignore # noqa
from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity # type: ignore # noqa
from materialyoucolor.scheme.scheme_content import SchemeContent # type: ignore # noqa
import hashlib
import pickle
import numpy as np
import re
schemes = {
"tonalSpot": SchemeTonalSpot,
"expressive": SchemeExpressive,
@@ -41,22 +40,21 @@ def join(*args):
return os.path.join(*args)
home = os.path.expanduser('~')
home = os.path.expanduser("~")
script_dir = os.path.dirname(os.path.abspath(__file__))
cache_path = f"{home}/.cache/material/"
os.makedirs(cache_path, exist_ok=True)
def rgb_to_hex(rgb):
return '#{:02x}{:02x}{:02x}'.format(*rgb[:3])
return "#{:02x}{:02x}{:02x}".format(*rgb[:3])
def rgba_to_rgb(rgba):
return f'{rgba[0]}, {rgba[1]}, {rgba[2]}'
return f"{rgba[0]}, {rgba[1]}, {rgba[2]}"
class Color():
class Color:
def __init__(self, scheme) -> None:
self.scheme = scheme
@@ -70,8 +68,12 @@ class Color():
class ColorsCache:
def __init__(
self, colors: DynamicScheme | dict, wallpaper: str,
original_color: int, contrast_level: int, scheme_name: str
self,
colors: DynamicScheme | dict,
wallpaper: str,
original_color: int,
contrast_level: int,
scheme_name: str,
) -> None:
self.colors: dict[str, str] = {}
self.wallpaper = wallpaper
@@ -90,14 +92,13 @@ class ColorsCache:
def colors_dict(cache: ColorsCache):
dict = {
return {
"wallpaper": cache.wallpaper,
"colors": cache.colors,
"original_color": cache.original_color,
"contrast_level": cache.contrast_level,
"scheme_name": cache.scheme_name
"scheme_name": cache.scheme_name,
}
return dict
def get_cache_object(object: dict | str):
@@ -108,10 +109,7 @@ def get_cache_object(object: dict | str):
original_color = object["original_color"]
contrast_level = object.get("contrast_level", 0)
scheme_name = object.get("scheme_name", "tonalSpot")
return ColorsCache(
colors, wallpaper, original_color,
contrast_level, scheme_name
)
return ColorsCache(colors, wallpaper, original_color, contrast_level, scheme_name)
def get_file_list(folder_path):
@@ -124,21 +122,18 @@ def get_file_list(folder_path):
ready_templates = {
"colors.css": "@define-color {name} {hex};\n",
"colors.scss": "${name}: {hex};\n"
}
additional = {
"onBackground": "foreground"
"colors.scss": "${name}: {hex};\n",
}
additional = {"onBackground": "foreground"}
def generate_color_map(scheme: DynamicScheme):
color_map: dict[str, str] = {}
for color in vars(MaterialDynamicColors).keys():
color_name = getattr(MaterialDynamicColors, color)
if hasattr(color_name, "get_hct"):
rgba = color_name.get_hct(scheme).to_rgba()
color_map[color.strip()] = rgb_to_hex(rgba)
return color_map
def generate_color_map(scheme):
colors = {}
for color_name in vars(MaterialDynamicColors).keys():
color_obj = getattr(MaterialDynamicColors, color_name)
if hasattr(color_obj, "get_hct"):
colors[color_name] = rgb_to_hex(color_obj.get_hct(scheme).to_rgba())
return colors
class ColorFormatter:
@@ -147,25 +142,23 @@ class ColorFormatter:
def apply_transformations(self, value, transformations):
intermediate_transforms = [
t for t in transformations
if not (t.startswith("strip") or t == "rgb")
t for t in transformations if not (t.startswith("strip") or t == "rgb")
]
final_transforms = [
t for t in transformations
if t.startswith("strip") or t == "rgb"
t for t in transformations if t.startswith("strip") or t == "rgb"
]
for transform in intermediate_transforms:
if transform.startswith("lighten"):
percent = int(re.search(r'\d+', transform).group())
percent = int(re.search(r"\d+", transform).group())
value = self.adjust_brightness(value, percent)
elif transform.startswith("darken"):
percent = int(re.search(r'\d+', transform).group())
percent = int(re.search(r"\d+", transform).group())
value = self.adjust_brightness(value, -percent)
for transform in final_transforms:
if transform.startswith("strip"):
value = value.lstrip('#')
value = value.lstrip("#")
elif transform == "rgb":
value = self.hex_to_rgb(value)
@@ -175,24 +168,20 @@ class ColorFormatter:
def min_max(v):
return min(255, max(0, v))
hex_color = hex_color.lstrip('#')
r, g, b = (int(hex_color[i:i+2], 16) for i in (0, 2, 4))
hex_color = hex_color.lstrip("#")
r, g, b = (int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
r = min_max(int(r * (1 + factor / 100)))
g = min_max(int(g * (1 + factor / 100)))
b = min_max(int(b * (1 + factor / 100)))
return f'#{r:02X}{g:02X}{b:02X}'.lower()
return f"#{r:02X}{g:02X}{b:02X}".lower()
def hex_to_rgb(self, hex_color):
hex_color = hex_color.lstrip('#')
r, g, b = (int(hex_color[i:i+2], 16) for i in (0, 2, 4))
return f'{r},{g},{b}'
hex_color = hex_color.lstrip("#")
r, g, b = (int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
return f"{r},{g},{b}"
def parse_transformations(self, transformations_str):
transformations = re.findall(
r'(\w+)(?:\((\d*)\))?', transformations_str
)
transformations = re.findall(r"(\w+)(?:\((\d*)\))?", transformations_str)
result = []
for command, arg in transformations:
if command:
@@ -203,7 +192,7 @@ class ColorFormatter:
return result
def format(self, text):
pattern = r'(<(\w+)(?:\.(.+))?>)'
pattern = r"(<(\w+)(?:\.(.+))?>)"
matches = re.findall(pattern, text)
result = []
last_end = 0
@@ -211,27 +200,28 @@ class ColorFormatter:
for match in matches:
full_match, key, transformations_str = match
if key in self.color_map:
result.append(text[last_end:text.find(full_match, last_end)])
result.append(text[last_end : text.find(full_match, last_end)])
value = self.color_map[key]
if transformations_str:
transformations = self.parse_transformations(
transformations_str
)
transformations = self.parse_transformations(transformations_str)
value = self.apply_transformations(value, transformations)
result.append(value)
last_end = text.find(full_match, last_end) + len(full_match)
result.append(text[last_end:])
result = ''.join(result)
pattern = r'<\\\\([^>]+)>'
result = re.sub(pattern, r'<\1>', result)
result = "".join(result)
pattern = r"<\\\\([^>]+)>"
result = re.sub(pattern, r"<\1>", result)
return result
def generate_templates(folder: str, output_folder: str, scheme: DynamicScheme,
color_scheme: str, wallpaper: str = ''):
def generate_templates(
folder: str,
output_folder: str,
scheme: DynamicScheme,
color_scheme: str,
wallpaper: str = "",
):
if not os.path.exists(output_folder):
os.makedirs(output_folder)
if not os.path.exists(folder):
@@ -244,13 +234,12 @@ def generate_templates(folder: str, output_folder: str, scheme: DynamicScheme,
formatter = ColorFormatter(scheme)
template = formatter.format(template)
template = (
template
.replace("<color-scheme>", color_scheme)
template.replace("<color-scheme>", color_scheme)
.replace("<output-folder>", output_folder)
.replace("<wallpaper>", wallpaper)
)
new_path = join(output_folder, os.path.basename(file_path))
with open(new_path, 'w') as f:
with open(new_path, "w") as f:
f.write(template)
for file in ready_templates:
@@ -262,21 +251,17 @@ def generate_templates(folder: str, output_folder: str, scheme: DynamicScheme,
hex_color = rgb_to_hex(rgba)
rgb_color = rgba_to_rgb(rgba)
new_line = ready_templates[file].format(
name=color,
hex=hex_color,
rgb=rgb_color
name=color, hex=hex_color, rgb=rgb_color
)
_template += new_line
if color in additional:
new_line = ready_templates[file].format(
name=additional[color],
hex=hex_color,
rgb=rgb_color
name=additional[color], hex=hex_color, rgb=rgb_color
)
_template += new_line
new_path = join(output_folder, os.path.basename(file))
with open(new_path, 'w') as f:
with open(new_path, "w") as f:
f.write(_template)
@@ -286,10 +271,10 @@ def run_hooks(folder: str):
file_list = get_file_list(folder)
for file_path in file_list:
file_name = os.path.basename(file_path)
if str(file_name).endswith(".py"):
subprocess.run(['python', '-O', file_path])
elif str(file_name).endswith(".sh"):
subprocess.run(['sh', file_path])
if file_name.endswith(".py"):
subprocess.run(["python", "-O", file_path])
elif file_name.endswith(".sh"):
subprocess.run(["sh", file_path])
def process_image(image_path, quality=2, num_colors=128):
@@ -297,81 +282,69 @@ def process_image(image_path, quality=2, num_colors=128):
global cache_path
_cache_path = join(cache_path, "cached_colors")
hash_object = hashlib.md5(image_path.encode())
cache_filename = hash_object.hexdigest() + '.pkl'
cache_filename = hash_object.hexdigest() + ".pkl"
os.makedirs(_cache_path, exist_ok=True)
return join(_cache_path, cache_filename)
def load_from_cache(cache_path):
if os.path.exists(cache_path):
with open(cache_path, 'rb') as f:
with open(cache_path, "rb") as f:
return pickle.load(f)
return None
def save_to_cache(cache_path, data):
with open(cache_path, 'wb') as f:
with open(cache_path, "wb") as f:
pickle.dump(data, f)
cache_path = get_cache_path(image_path)
cached_result = load_from_cache(cache_path)
cache_file = get_cache_path(image_path)
cached_result = load_from_cache(cache_file)
if cached_result is not None:
return cached_result
image = Image.open(image_path).convert('RGB')
image = Image.open(image_path).convert("RGB")
image_data = np.array(image)
pixel_array = image_data[::quality, ::quality].reshape(-1, 3)
result = QuantizeCelebi(pixel_array, num_colors)
color = Score.score(result)[0]
save_to_cache(cache_path, color)
save_to_cache(cache_file, color)
return color
def main(
color_scheme: str, image_path: str, use_color: int | None = None,
scheme_name: str | None = None, contrast_level: int = 0
color_scheme: str,
image_path: str,
use_color: int | None = None,
scheme_name: str | None = None,
contrast_level: int = 0,
):
scheme_name = scheme_name or "tonalSpot"
scheme = schemes[scheme_name]
is_dark = True if color_scheme == "dark" else False
if use_color is None:
color = process_image(image_path, 4, 1024)
else:
color = use_color
scheme = scheme(
Hct.from_int(color),
is_dark,
contrast_level
)
scheme = scheme(Hct.from_int(color), is_dark, contrast_level)
# for color in vars(MaterialDynamicColors).keys():
# color_name = getattr(MaterialDynamicColors, color)
# if hasattr(color_name, "get_hct"):
# print(color, rgb_to_hex(color_name.get_hct(scheme).to_rgba()))
with open(join(cache_path, "colors.json"), 'w') as f:
object = ColorsCache(
scheme, image_path, color, contrast_level, scheme_name
with open(join(cache_path, "colors.json"), "w") as f:
cache_obj = ColorsCache(
generate_color_map(scheme), image_path, color, contrast_level, scheme_name
)
json.dump(colors_dict(object), f, indent=2)
json.dump(colors_dict(cache_obj), f, indent=2)
generate_templates(
f"{script_dir}/templates", cache_path, scheme, color_scheme, image_path
)
generate_templates(
f"{script_dir}/templates/svg", join(cache_path, "svg/"), scheme,
color_scheme, image_path
)
if is_dark:
subprocess.run(['sh', join(script_dir, 'gtk-material.sh')])
subprocess.run(["sh", join(script_dir, "gtk-material.sh")])
else:
subprocess.run(['sh', join(script_dir, 'gtk-material.sh'), '--light'])
subprocess.run(["sh", join(script_dir, "gtk-material.sh"), "--light"])
run_hooks(join(script_dir, "hooks"))
@@ -379,30 +352,29 @@ def main(
def _argparse() -> tuple[bool, bool, str, str, str, str]:
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("-R", help="Restore previous colorscheme.", action="store_true")
group.add_argument(
"-R", help="Restore previous colorscheme.", action='store_true'
)
group.add_argument(
"-w", help="Use last used wallpaper for color generation.",
action='store_true'
)
group.add_argument(
"--image", type=str, help="Image for color scheme generation"
"-w", help="Use last used wallpaper for color generation.", action="store_true"
)
group.add_argument("--image", type=str, help="Image for color scheme generation")
group.add_argument(
"--color", type=str, help="Main color for color scheme generation"
)
parser.add_argument(
'--color-scheme', type=str, default='dark', choices=['dark', 'light'],
help="Color scheme variant"
"--color-scheme",
type=str,
default="dark",
choices=["dark", "light"],
help="Color scheme variant",
)
parser.add_argument(
'--scheme', type=str, default="",
choices=list(schemes.keys()), help="Scheme for color generation"
"--scheme",
type=str,
default="",
choices=list(schemes.keys()),
help="Scheme for color generation",
)
args = parser.parse_args()
restore_palette = args.R
use_last_used_wallpaper = args.w
image = args.image
@@ -410,16 +382,19 @@ def _argparse() -> tuple[bool, bool, str, str, str, str]:
use_color = args.color
scheme = args.scheme
return (
restore_palette, use_last_used_wallpaper, image,
color_scheme, use_color, scheme
restore_palette,
use_last_used_wallpaper,
image,
color_scheme,
use_color,
scheme,
)
def _main():
(
restore_palette, use_last_used_wallpaper, image,
color_scheme, use_color, scheme
) = _argparse()
restore_palette, use_last_used_wallpaper, image, color_scheme, use_color, scheme = (
_argparse()
)
if not scheme:
scheme = None
if use_color is not None:
@@ -427,15 +402,18 @@ def _main():
with open(join(cache_path, "colors.json")) as f:
content = get_cache_object(f.read())
main(
color_scheme, image_path=content.wallpaper,
use_color=int(use_color.lstrip('#'), 16), scheme_name=scheme
color_scheme,
image_path=content.wallpaper,
use_color=int(use_color.lstrip("#"), 16),
scheme_name=scheme,
)
except Exception:
main(
color_scheme, image_path="/dev/null",
use_color=int(use_color.lstrip('#'), 16), scheme_name=scheme
color_scheme,
image_path="/dev/null",
use_color=int(use_color.lstrip("#"), 16),
scheme_name=scheme,
)
elif restore_palette:
with open(join(cache_path, "colors.json")) as f:
content = get_cache_object(f.read())
@@ -444,7 +422,7 @@ def _main():
image_path=content.wallpaper,
use_color=content.original_color,
scheme_name=scheme or content.scheme_name,
contrast_level=content.contrast_level
contrast_level=content.contrast_level,
)
elif use_last_used_wallpaper:
with open(join(cache_path, "colors.json")) as f:
@@ -453,13 +431,12 @@ def _main():
color_scheme,
image_path=content.wallpaper,
scheme_name=scheme or content.scheme_name,
contrast_level=content.contrast_level
contrast_level=content.contrast_level,
)
else:
if os.path.isdir(image):
files = [
f for f in os.listdir(image)
if f.endswith(('.png', '.jpg', '.jpeg'))
f for f in os.listdir(image) if f.endswith((".png", ".jpg", ".jpeg"))
]
if files:
image = join(image, random.choice(files))
@@ -392,7 +392,7 @@ msgFileOutBgSelected: ACCENT; // Outgoing selected audio file
msgFile1Bg: #72b1df; // Blue shared links / files without image square thumbnail
msgFile1BgDark: #5c9ece; // Blue shared files without image download circle background
msgFile1BgOver: #5294c4; // Blue shared files without image download circle background with mouse over
msgFile1BgSelected: #5099d0; // Blue shared files without image download circle background if file is selected
msgFile1BgSelected: #50100d0; // Blue shared files without image download circle background if file is selected
msgFile2Bg: #61b96e; // Green shared links / shared files without image square thumbnail
msgFile2BgDark: #4da859; // Green shared files without image download circle background
msgFile2BgOver: #44a050; // Green shared files without image download circle background with mouse over
@@ -514,7 +514,7 @@ mediaviewFileSizeFg: ON_BACKGROUND_DARKER; // File size text in file rectan
mediaviewFileRedCornerFg: #d55959; // Red file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .pdf)
mediaviewFileYellowCornerFg: #e8a659; // Yellow file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .zip)
mediaviewFileGreenCornerFg: #49a957; // Green file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .exe)
mediaviewFileBlueCornerFg: #599dcf; // Blue file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .dmg)
mediaviewFileBlueCornerFg: #5100dcf; // Blue file thumbnail placeholder corner in file rectangle (for a file without thumbnail, like .dmg)
mediaviewFileExtFg: activeButtonFg; // File extension text in file thumbnail placeholder in file rectangle
mediaviewMenuBg: BACKGROUND; // Context menu in Media Viewer background
@@ -1,14 +1,14 @@
@use "sass:color";
@use "sass:math";
@use "<output-folder>colors.scss" as *;
@import url(https://capnkitten.github.io/BetterDiscord/Themes/Material-Discord/css/source.css);
@import "<output-folder>/colors.scss";
$color-scheme: <color-scheme>;
$hue: round(color.channel($primary, "hue", $space: hsl));
$saturation: round(color.channel($primary, "saturation", $space: hsl));
$lightness: round(color.channel($primary, "lightness", $space: hsl));
$hue: math.round(color.channel($primary, "hue", $space: hsl));
$saturation: math.round(color.channel($primary, "saturation", $space: hsl));
$lightness: math.round(color.channel($primary, "lightness", $space: hsl));
* {
transition: color 0.2s ease-in-out;
+1 -1
View File
@@ -1,7 +1,7 @@
from fabric.audio import Audio
from .brightness import Brightness
from .screen_record import ScreenRecorder
from .wifi import *
from .network import *
brightness = Brightness()
+279
View File
@@ -0,0 +1,279 @@
import contextlib
import gi
from fabric.core.service import Property, Service, Signal
from fabric.utils import bulk_connect
from gi.repository import GLib # type: ignore
from loguru import logger
class PlayerctlImportError(ImportError):
"""An error to raise when playerctl is not installed."""
def __init__(self, *args):
super().__init__(
"Playerctl is not installed, please install it first",
*args,
)
try:
gi.require_version("Playerctl", "2.0")
from gi.repository import Playerctl
# from gi.repository import Playerctl # type: ignore
except ValueError:
raise PlayerctlImportError
class MprisPlayer(Service):
"""A service to manage a mpris player."""
@Signal
def exit(self, value: bool) -> bool: ...
@Signal
def changed(self) -> None: ...
def __init__(
self,
player: Playerctl.Player,
**kwargs,
):
self._signal_connectors: dict = {}
self._player: Playerctl.Player = player
super().__init__(**kwargs)
for sn in ["playback-status", "loop-status", "shuffle", "volume", "seeked"]:
self._signal_connectors[sn] = self._player.connect(
sn,
lambda *args, sn=sn: self.notifier(sn, args),
)
self._signal_connectors["exit"] = self._player.connect(
"exit",
self.on_player_exit,
)
self._signal_connectors["metadata"] = self._player.connect(
"metadata",
lambda *args: self.update_status(),
)
GLib.idle_add(lambda *args: self.update_status_once())
def update_status(self):
# schedule each notifier asynchronously.
def notify_property(prop):
if self.get_property(prop) is not None:
self.notifier(prop)
for prop in [
"metadata",
"title",
"artist",
"arturl",
"length",
]:
GLib.idle_add(lambda p=prop: (notify_property(p), False))
for prop in [
"can-seek",
"can-pause",
"can-shuffle",
"can-go-next",
"can-go-previous",
]:
GLib.idle_add(lambda p=prop: (self.notifier(p), False))
def update_status_once(self):
# schedule notifier calls for each property
def notify_all():
for prop in self.list_properties(): # type: ignore
self.notifier(prop.name)
return False
GLib.idle_add(notify_all, priority=GLib.PRIORITY_DEFAULT_IDLE)
def notifier(self, name: str, args=None):
def notify_and_emit():
self.notify(name)
self.emit("changed")
return False
GLib.idle_add(notify_and_emit, priority=GLib.PRIORITY_DEFAULT_IDLE)
def on_player_exit(self, player):
for id in list(self._signal_connectors.values()):
with contextlib.suppress(Exception):
self._player.disconnect(id)
del self._signal_connectors
GLib.idle_add(lambda: (self.emit("exit", True), False))
del self._player
def toggle_shuffle(self):
if self.can_shuffle:
# schedule the shuffle toggle in the GLib idle loop
GLib.idle_add(lambda: (setattr(self, "shuffle", not self.shuffle), False))
# else do nothing
def play_pause(self):
if self.can_pause:
GLib.idle_add(lambda: (self._player.play_pause(), False))
def next(self):
if self.can_go_next:
GLib.idle_add(lambda: (self._player.next(), False))
def previous(self):
if self.can_go_previous:
GLib.idle_add(lambda: (self._player.previous(), False))
# Properties
@Property(str, "readable")
def player_name(self) -> int:
return self._player.get_property("player-name") # type: ignore
@Property(int, "read-write", default_value=0)
def position(self) -> int:
return self._player.get_property("position") # type: ignore
@position.setter
def position(self, new_pos: int):
self._player.set_position(new_pos)
@Property(object, "readable")
def metadata(self) -> dict:
return self._player.get_property("metadata") # type: ignore
@Property(str or None, "readable")
def arturl(self) -> str | None:
if "mpris:artUrl" in self.metadata.keys(): # type: ignore # noqa: SIM118
return self.metadata["mpris:artUrl"] # type: ignore
return None
@Property(str or None, "readable")
def length(self) -> str | None:
if "mpris:length" in self.metadata.keys(): # type: ignore # noqa: SIM118
return self.metadata["mpris:length"] # type: ignore
return None
@Property(str, "readable")
def artist(self) -> str:
artist = self._player.get_artist() # type: ignore
if isinstance(artist, (list, tuple)):
return ", ".join(artist)
return artist
@Property(str, "readable")
def album(self) -> str:
return self._player.get_album() # type: ignore
@Property(str, "readable")
def title(self):
return self._player.get_title()
@Property(bool, "read-write", default_value=False)
def shuffle(self) -> bool:
return self._player.get_property("shuffle") # type: ignore
@shuffle.setter
def shuffle(self, do_shuffle: bool):
self.notifier("shuffle")
return self._player.set_shuffle(do_shuffle)
@Property(str, "readable")
def playback_status(self) -> str:
return {
Playerctl.PlaybackStatus.PAUSED: "paused",
Playerctl.PlaybackStatus.PLAYING: "playing",
Playerctl.PlaybackStatus.STOPPED: "stopped",
}.get(self._player.get_property("playback_status"), "unknown") # type: ignore
@Property(str, "read-write")
def loop_status(self) -> str:
return {
Playerctl.LoopStatus.NONE: "none",
Playerctl.LoopStatus.TRACK: "track",
Playerctl.LoopStatus.PLAYLIST: "playlist",
}.get(self._player.get_property("loop_status"), "unknown") # type: ignore
@loop_status.setter
def loop_status(self, status: str):
loop_status = {
"none": Playerctl.LoopStatus.NONE,
"track": Playerctl.LoopStatus.TRACK,
"playlist": Playerctl.LoopStatus.PLAYLIST,
}.get(status)
self._player.set_loop_status(loop_status) if loop_status else None
@Property(bool, "readable", default_value=False)
def can_go_next(self) -> bool:
return self._player.get_property("can_go_next") # type: ignore
@Property(bool, "readable", default_value=False)
def can_go_previous(self) -> bool:
return self._player.get_property("can_go_previous") # type: ignore
@Property(bool, "readable", default_value=False)
def can_seek(self) -> bool:
return self._player.get_property("can_seek") # type: ignore
@Property(bool, "readable", default_value=False)
def can_pause(self) -> bool:
return self._player.get_property("can_pause") # type: ignore
@Property(bool, "readable", default_value=False)
def can_shuffle(self) -> bool:
try:
self._player.set_shuffle(self._player.get_property("shuffle"))
return True
except Exception:
return False
@Property(bool, "readable", default_value=False)
def can_loop(self) -> bool:
try:
self._player.set_shuffle(self._player.get_property("shuffle"))
return True
except Exception:
return False
class MprisPlayerManager(Service):
"""A service to manage mpris players."""
@Signal
def player_appeared(self, player: Playerctl.Player) -> Playerctl.Player: ...
@Signal
def player_vanished(self, player_name: str) -> str: ...
def __init__(
self,
**kwargs,
):
self._manager = Playerctl.PlayerManager.new()
bulk_connect(
self._manager,
{
"name-appeared": self.on_name_appeard,
"name-vanished": self.on_name_vanished,
},
)
self.add_players()
super().__init__(**kwargs)
def on_name_appeard(self, manager, player_name: Playerctl.PlayerName):
logger.info(f"[MprisPlayer] {player_name.name} appeared")
new_player = Playerctl.Player.new_from_name(player_name)
manager.manage_player(new_player)
self.emit("player-appeared", new_player) # type: ignore
def on_name_vanished(self, manager, player_name: Playerctl.PlayerName):
logger.info(f"[MprisPlayer] {player_name.name} vanished")
self.emit("player-vanished", player_name.name) # type: ignore
def add_players(self):
for player in self._manager.get_property("player-names"): # type: ignore
self._manager.manage_player(Playerctl.Player.new_from_name(player)) # type: ignore
@Property(object, "readable")
def players(self):
return self._manager.get_property("players") # type: ignore
File renamed without changes.