feat(update): huge changes
This commit is contained in:
98 files changed
+9636
-5575
No files matched your search
@@ -1,33 +1,21 @@
|
||||
import hashlib, json, os, colorsys
|
||||
from PIL import Image
|
||||
import os
|
||||
import hashlib
|
||||
from gi.repository import GdkPixbuf, Gtk, GLib, Gio, Gdk
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.entry import Entry
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from gi.repository import GdkPixbuf, GLib, Gtk, Gio, Gdk
|
||||
from fabric.utils import get_relative_path, exec_shell_command_async
|
||||
from concurrent.futures import ThreadPoolExecutor, wait
|
||||
from utils import WALLPAPERS_DIR
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.utils.helpers import exec_shell_command_async
|
||||
from PIL import Image
|
||||
import concurrent.futures
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import utils.icons as icons
|
||||
import config.data as data
|
||||
|
||||
|
||||
class WallpaperSelector(Box):
|
||||
CACHE_DIR = os.path.expanduser("~/.cache/modus/wallpapers")
|
||||
SETTINGS_FILE = get_relative_path("../../../config/assets/settings.json")
|
||||
CURRENT_WALLPAPER_FILE = os.path.expanduser("~/.cache/current_wallpaper")
|
||||
SCHEMES = {
|
||||
"TonalSpot": "tonalSpot",
|
||||
"Expressive": "expressive",
|
||||
"FruitSalad": "fruitSalad",
|
||||
"Monochrome": "monochrome",
|
||||
"Rainbow": "rainbow",
|
||||
"Vibrant": "vibrant",
|
||||
"Neutral": "neutral",
|
||||
"Fidelity": "fidelity",
|
||||
"Content": "content",
|
||||
}
|
||||
ITEM_WIDTH = 108
|
||||
CACHE_DIR = f"{data.CACHE_DIR}/thumbnails"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(
|
||||
@@ -38,33 +26,44 @@ class WallpaperSelector(Box):
|
||||
v_expand=False,
|
||||
**kwargs,
|
||||
)
|
||||
self.launcher = kwargs["launcher"]
|
||||
os.makedirs(self.CACHE_DIR, exist_ok=True)
|
||||
self.files = [f for f in os.listdir(WALLPAPERS_DIR) if self._is_image(f)]
|
||||
self.thumbnails, self.thumbnail_queue = [], []
|
||||
self.executor = ThreadPoolExecutor(max_workers=4)
|
||||
self.selected_index, self.current_wallpaper = -1, None
|
||||
self._init_widgets()
|
||||
self._start_thumbnail_thread()
|
||||
self.setup_file_monitor()
|
||||
if os.path.exists(self.CURRENT_WALLPAPER_FILE):
|
||||
gfile = Gio.File.new_for_path(self.CURRENT_WALLPAPER_FILE)
|
||||
self.current_wp_monitor = gfile.monitor_file(
|
||||
Gio.FileMonitorFlags.NONE, None
|
||||
)
|
||||
self.current_wp_monitor.connect(
|
||||
"changed", self.on_current_wallpaper_changed
|
||||
)
|
||||
self.show_all()
|
||||
self.search_entry.grab_focus()
|
||||
|
||||
def _init_widgets(self):
|
||||
with os.scandir(data.WALLPAPERS_DIR) as entries:
|
||||
for entry in entries:
|
||||
if entry.is_file() and self._is_image(entry.name):
|
||||
# Check if the file needs renaming: file should be lowercase and have hyphens instead of spaces
|
||||
if entry.name != entry.name.lower() or " " in entry.name:
|
||||
new_name = entry.name.lower().replace(" ", "-")
|
||||
full_path = os.path.join(data.WALLPAPERS_DIR, entry.name)
|
||||
new_full_path = os.path.join(data.WALLPAPERS_DIR, new_name)
|
||||
try:
|
||||
os.rename(full_path, new_full_path)
|
||||
print(
|
||||
f"Renamed old wallpaper '{full_path}' to '{new_full_path}'"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error renaming file {full_path}: {e}")
|
||||
|
||||
# Refresh the file list after possible renaming
|
||||
self.files = sorted(
|
||||
[f for f in os.listdir(data.WALLPAPERS_DIR) if self._is_image(f)]
|
||||
)
|
||||
self.thumbnails = []
|
||||
self.thumbnail_queue = []
|
||||
self.executor = ThreadPoolExecutor(max_workers=4) # Shared executor
|
||||
|
||||
# Variable to control the selection (similar to AppLauncher)
|
||||
self.selected_index = -1
|
||||
|
||||
# Initialize UI components
|
||||
self.viewport = Gtk.IconView(name="wallpaper-icons")
|
||||
self.viewport.set_model(Gtk.ListStore(GdkPixbuf.Pixbuf, str))
|
||||
self.viewport.set_pixbuf_column(0)
|
||||
# Hide text column so only the image is shown
|
||||
self.viewport.set_text_column(-1)
|
||||
self.viewport.set_item_width(self.ITEM_WIDTH)
|
||||
self.viewport.set_item_width(0)
|
||||
self.viewport.connect("item-activated", self.on_wallpaper_selected)
|
||||
|
||||
self.scrolled_window = ScrolledWindow(
|
||||
name="scrolled-window",
|
||||
spacing=10,
|
||||
@@ -72,117 +71,122 @@ class WallpaperSelector(Box):
|
||||
v_expand=True,
|
||||
child=self.viewport,
|
||||
)
|
||||
|
||||
self.search_entry = Entry(
|
||||
name="search-entry-walls",
|
||||
placeholder="Search Wallpapers...",
|
||||
h_expand=True,
|
||||
notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
|
||||
on_key_press_event=self.on_search_entry_key_press,
|
||||
)
|
||||
self.search_entry.props.xalign = 0.5
|
||||
self.search_entry.connect("focus-out-event", self.on_search_entry_focus_out)
|
||||
|
||||
self.schemes = {
|
||||
"scheme-tonal-spot": "Tonal Spot",
|
||||
"scheme-content": "Content",
|
||||
"scheme-expressive": "Expressive",
|
||||
"scheme-fidelity": "Fidelity",
|
||||
"scheme-fruit-salad": "Fruit Salad",
|
||||
"scheme-monochrome": "Monochrome",
|
||||
"scheme-neutral": "Neutral",
|
||||
"scheme-rainbow": "Rainbow",
|
||||
}
|
||||
|
||||
self.scheme_dropdown = Gtk.ComboBoxText()
|
||||
self.scheme_dropdown.set_name("scheme-dropdown")
|
||||
self.scheme_dropdown.set_tooltip_text("Select color scheme")
|
||||
for display_name, scheme_id in sorted(self.SCHEMES.items()):
|
||||
self.scheme_dropdown.append(scheme_id, display_name)
|
||||
self.scheme_dropdown.set_active_id("tonalSpot")
|
||||
for key, display_name in self.schemes.items():
|
||||
self.scheme_dropdown.append(key, display_name)
|
||||
self.scheme_dropdown.set_active_id("scheme-tonal-spot")
|
||||
self.scheme_dropdown.connect("changed", self.on_scheme_changed)
|
||||
self.materialyoucolor_switcher = Gtk.Switch(name="materialyoucolor-switcher")
|
||||
self.materialyoucolor_switcher.set_vexpand(False)
|
||||
self.materialyoucolor_switcher.set_hexpand(False)
|
||||
self.materialyoucolor_switcher.set_valign(Gtk.Align.CENTER)
|
||||
self.materialyoucolor_switcher.set_halign(Gtk.Align.CENTER)
|
||||
self.materialyoucolor_switcher.set_active(True)
|
||||
|
||||
# Create a switcher to enable/disable Matugen (enabled by default)
|
||||
self.matugen_switcher = Gtk.Switch(name="matugen-switcher")
|
||||
self.matugen_switcher.set_vexpand(False)
|
||||
self.matugen_switcher.set_hexpand(False)
|
||||
self.matugen_switcher.set_valign(Gtk.Align.CENTER)
|
||||
self.matugen_switcher.set_halign(Gtk.Align.CENTER)
|
||||
self.matugen_switcher.set_active(True)
|
||||
|
||||
self.mat_icon = Label(name="mat-label", markup=icons.palette)
|
||||
self.dropdown_box = Box(name="dropdown-box", children=[self.scheme_dropdown])
|
||||
self.custom_color_entry = Entry(
|
||||
name="entry-custom-color",
|
||||
h_expand=True,
|
||||
notify_text=lambda entry, *_: self.on_custom_color_submitted(entry),
|
||||
on_key_press_event=self.on_custom_color_key_press,
|
||||
)
|
||||
|
||||
# Add the switcher to the header_box's start_children
|
||||
self.header_box = CenterBox(
|
||||
name="header-box",
|
||||
spacing=10,
|
||||
spacing=8,
|
||||
orientation="h",
|
||||
start_children=[self.materialyoucolor_switcher, self.mat_icon],
|
||||
center_children=[
|
||||
self.search_entry,
|
||||
Label(label="Custom Color:"),
|
||||
self.custom_color_entry,
|
||||
],
|
||||
end_children=[self.dropdown_box],
|
||||
start_children=[self.matugen_switcher, self.mat_icon],
|
||||
center_children=[self.search_entry],
|
||||
end_children=[self.scheme_dropdown],
|
||||
)
|
||||
|
||||
self.add(self.header_box)
|
||||
self.add(self.scrolled_window)
|
||||
self._start_thumbnail_thread()
|
||||
self.setup_file_monitor() # Initialize file monitoring
|
||||
self.show_all()
|
||||
# Ensure the search entry gets focus when starting
|
||||
self.search_entry.grab_focus()
|
||||
|
||||
def setup_file_monitor(self):
|
||||
gfile = Gio.File.new_for_path(WALLPAPERS_DIR)
|
||||
gfile = Gio.File.new_for_path(data.WALLPAPERS_DIR)
|
||||
self.file_monitor = gfile.monitor_directory(Gio.FileMonitorFlags.NONE, None)
|
||||
self.file_monitor.connect("changed", self.on_directory_changed)
|
||||
|
||||
def on_current_wallpaper_changed(self, monitor, file, other_file, event_type):
|
||||
if event_type in (Gio.FileMonitorEvent.CHANGED, Gio.FileMonitorEvent.CREATED):
|
||||
try:
|
||||
with open(self.CURRENT_WALLPAPER_FILE, "r") as f:
|
||||
new_wp = f.read().strip()
|
||||
if new_wp and new_wp != self.current_wallpaper:
|
||||
self.current_wallpaper = new_wp
|
||||
if self.custom_color_entry.get_text().strip().lower() in [
|
||||
"",
|
||||
"none",
|
||||
]:
|
||||
scheme = self.scheme_dropdown.get_active_id()
|
||||
color_generator = get_relative_path(
|
||||
"../../../config/material-colors/generate.py"
|
||||
)
|
||||
command = f'python -O {color_generator} --scheme "{scheme}" --image "{self.current_wallpaper}"'
|
||||
self._run_command(command)
|
||||
except Exception as e:
|
||||
print(f"Error updating current wallpaper: {e}")
|
||||
|
||||
def on_directory_changed(self, monitor, file, other_file, event_type):
|
||||
file_name = file.get_basename()
|
||||
if event_type == Gio.FileMonitorEvent.DELETED:
|
||||
if file_name in self.files:
|
||||
self.files.remove(file_name)
|
||||
self._delete_cache(file_name)
|
||||
cache_path = self._get_cache_path(file_name)
|
||||
if os.path.exists(cache_path):
|
||||
try:
|
||||
os.remove(cache_path)
|
||||
except Exception as e:
|
||||
print(f"Error deleting cache {cache_path}: {e}")
|
||||
self.thumbnails = [(p, n) for p, n in self.thumbnails if n != file_name]
|
||||
GLib.idle_add(self.arrange_viewport, self.search_entry.get_text())
|
||||
return
|
||||
if event_type in (
|
||||
Gio.FileMonitorEvent.CREATED,
|
||||
Gio.FileMonitorEvent.CHANGED,
|
||||
) and self._is_image(file_name):
|
||||
if (
|
||||
event_type == Gio.FileMonitorEvent.CREATED
|
||||
and file_name not in self.files
|
||||
):
|
||||
self.files.append(file_name)
|
||||
self.files.sort()
|
||||
elif event_type == Gio.FileMonitorEvent.CHANGED and file_name in self.files:
|
||||
self._delete_cache(file_name)
|
||||
self.executor.submit(self._process_file, file_name)
|
||||
|
||||
def _delete_cache(self, file_name: str):
|
||||
cache_path = self._get_cache_path(file_name)
|
||||
if os.path.exists(cache_path):
|
||||
try:
|
||||
os.remove(cache_path)
|
||||
except Exception:
|
||||
pass
|
||||
elif event_type == Gio.FileMonitorEvent.CREATED:
|
||||
if self._is_image(file_name):
|
||||
# Convert filename to lowercase and replace spaces with "-"
|
||||
new_name = file_name.lower().replace(" ", "-")
|
||||
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
|
||||
new_full_path = os.path.join(data.WALLPAPERS_DIR, new_name)
|
||||
if new_name != file_name:
|
||||
try:
|
||||
os.rename(full_path, new_full_path)
|
||||
file_name = new_name
|
||||
print(f"Renamed file '{full_path}' to '{new_full_path}'")
|
||||
except Exception as e:
|
||||
print(f"Error renaming file {full_path}: {e}")
|
||||
if file_name not in self.files:
|
||||
self.files.append(file_name)
|
||||
self.files.sort()
|
||||
self.executor.submit(self._process_file, file_name)
|
||||
elif event_type == Gio.FileMonitorEvent.CHANGED:
|
||||
if self._is_image(file_name) and file_name in self.files:
|
||||
cache_path = self._get_cache_path(file_name)
|
||||
if os.path.exists(cache_path):
|
||||
try:
|
||||
os.remove(cache_path)
|
||||
except Exception as e:
|
||||
print(f"Error deleting cache for changed file {file_name}: {e}")
|
||||
self.executor.submit(self._process_file, file_name)
|
||||
|
||||
def arrange_viewport(self, query: str = ""):
|
||||
model = self.viewport.get_model()
|
||||
model.clear()
|
||||
for pixbuf, file_name in sorted(
|
||||
[
|
||||
(thumb, name)
|
||||
for thumb, name in self.thumbnails
|
||||
if query.casefold() in name.casefold()
|
||||
],
|
||||
key=lambda x: x[1].lower(),
|
||||
):
|
||||
filtered_thumbnails = [
|
||||
(thumb, name)
|
||||
for thumb, name in self.thumbnails
|
||||
if query.casefold() in name.casefold()
|
||||
]
|
||||
filtered_thumbnails.sort(key=lambda x: x[1].lower())
|
||||
for pixbuf, file_name in filtered_thumbnails:
|
||||
model.append([pixbuf, file_name])
|
||||
if not query.strip():
|
||||
# If the search entry is empty, no icon is selected; otherwise, select the first one.
|
||||
if query.strip() == "":
|
||||
self.viewport.unselect_all()
|
||||
self.selected_index = -1
|
||||
elif len(model) > 0:
|
||||
@@ -191,39 +195,40 @@ class WallpaperSelector(Box):
|
||||
def on_wallpaper_selected(self, iconview, path):
|
||||
model = iconview.get_model()
|
||||
file_name = model[path][1]
|
||||
full_path = os.path.join(WALLPAPERS_DIR, file_name)
|
||||
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
|
||||
selected_scheme = self.scheme_dropdown.get_active_id()
|
||||
wallpaper_script = get_relative_path("../../../config/scripts/wallpaper.py")
|
||||
if self.materialyoucolor_switcher.get_active():
|
||||
command = f"python -O {wallpaper_script} -I {full_path}"
|
||||
GLib.spawn_command_line_async(command)
|
||||
self.update_scheme(selected_scheme)
|
||||
if self.matugen_switcher.get_active():
|
||||
# Matugen is enabled: run the normal command.
|
||||
exec_shell_command_async(f"matugen image {full_path} -t {selected_scheme}")
|
||||
else:
|
||||
cmd = f"swww img {full_path} -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest"
|
||||
exec_shell_command_async(cmd)
|
||||
# Matugen is disabled: run the alternative swww command.
|
||||
exec_shell_command_async(
|
||||
f"swww img {full_path} -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest"
|
||||
)
|
||||
|
||||
def on_scheme_changed(self, combo):
|
||||
selected_scheme = combo.get_active_id()
|
||||
print(f"Color scheme selected: {selected_scheme}")
|
||||
|
||||
def on_search_entry_key_press(self, widget, event):
|
||||
if event.state & Gdk.ModifierType.SHIFT_MASK:
|
||||
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down):
|
||||
scheme_list = [
|
||||
scheme_id for _, scheme_id in sorted(self.SCHEMES.items())
|
||||
]
|
||||
try:
|
||||
current_index = scheme_list.index(
|
||||
self.scheme_dropdown.get_active_id()
|
||||
)
|
||||
except ValueError:
|
||||
current_index = 0
|
||||
schemes_list = list(self.schemes.keys())
|
||||
current_id = self.scheme_dropdown.get_active_id()
|
||||
current_index = (
|
||||
schemes_list.index(current_id) if current_id in schemes_list else 0
|
||||
)
|
||||
new_index = (
|
||||
(current_index - 1) % len(scheme_list)
|
||||
(current_index - 1) % len(schemes_list)
|
||||
if event.keyval == Gdk.KEY_Up
|
||||
else (current_index + 1) % len(scheme_list)
|
||||
else (current_index + 1) % len(schemes_list)
|
||||
)
|
||||
self.scheme_dropdown.set_active(new_index)
|
||||
return True
|
||||
elif event.keyval == Gdk.KEY_Right:
|
||||
self.scheme_dropdown.popup()
|
||||
return True
|
||||
|
||||
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
|
||||
self.move_selection_2d(event.keyval)
|
||||
return True
|
||||
@@ -239,6 +244,7 @@ class WallpaperSelector(Box):
|
||||
total_items = len(model)
|
||||
if total_items == 0:
|
||||
return
|
||||
|
||||
if self.selected_index == -1:
|
||||
new_index = (
|
||||
0 if keyval in (Gdk.KEY_Down, Gdk.KEY_Right) else total_items - 1
|
||||
@@ -246,39 +252,45 @@ class WallpaperSelector(Box):
|
||||
else:
|
||||
current_index = self.selected_index
|
||||
allocation = self.viewport.get_allocation()
|
||||
columns = max(1, allocation.width // self.ITEM_WIDTH)
|
||||
new_index = current_index + (
|
||||
1
|
||||
if keyval == Gdk.KEY_Right
|
||||
else -1
|
||||
if keyval == Gdk.KEY_Left
|
||||
else columns
|
||||
if keyval == Gdk.KEY_Down
|
||||
else -columns
|
||||
)
|
||||
new_index = max(0, min(new_index, total_items - 1))
|
||||
item_width = 108 # Approximate item width including margins
|
||||
columns = max(1, allocation.width // item_width)
|
||||
if keyval == Gdk.KEY_Right:
|
||||
new_index = current_index + 1
|
||||
elif keyval == Gdk.KEY_Left:
|
||||
new_index = current_index - 1
|
||||
elif keyval == Gdk.KEY_Down:
|
||||
new_index = current_index + columns
|
||||
elif keyval == Gdk.KEY_Up:
|
||||
new_index = current_index - columns
|
||||
if new_index < 0:
|
||||
new_index = 0
|
||||
if new_index >= total_items:
|
||||
new_index = total_items - 1
|
||||
|
||||
self.update_selection(new_index)
|
||||
|
||||
def update_selection(self, new_index: int):
|
||||
self.viewport.unselect_all()
|
||||
path = Gtk.TreePath.new_from_indices([new_index])
|
||||
self.viewport.select_path(path)
|
||||
self.viewport.scroll_to_path(path, False, 0.5, 0.5)
|
||||
self.viewport.scroll_to_path(
|
||||
path, False, 0.5, 0.5
|
||||
) # Ensure the selected icon is visible
|
||||
self.selected_index = new_index
|
||||
|
||||
def _start_thumbnail_thread(self):
|
||||
GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
|
||||
thread = GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
|
||||
|
||||
def _preload_thumbnails(self, _data):
|
||||
futures = [
|
||||
self.executor.submit(self._process_file, file_name)
|
||||
for file_name in self.files
|
||||
]
|
||||
wait(futures)
|
||||
concurrent.futures.wait(futures)
|
||||
GLib.idle_add(self._process_batch)
|
||||
|
||||
def _process_file(self, file_name):
|
||||
full_path = os.path.join(WALLPAPERS_DIR, file_name)
|
||||
full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
|
||||
cache_path = self._get_cache_path(file_name)
|
||||
if not os.path.exists(cache_path):
|
||||
try:
|
||||
@@ -287,10 +299,13 @@ class WallpaperSelector(Box):
|
||||
side = min(width, height)
|
||||
left = (width - side) // 2
|
||||
top = (height - side) // 2
|
||||
img_cropped = img.crop((left, top, left + side, top + side))
|
||||
right = left + side
|
||||
bottom = top + side
|
||||
img_cropped = img.crop((left, top, right, bottom))
|
||||
img_cropped.thumbnail((96, 96), Image.Resampling.LANCZOS)
|
||||
img_cropped.save(cache_path, "PNG")
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
print(f"Error processing {file_name}: {e}")
|
||||
return
|
||||
self.thumbnail_queue.append((cache_path, file_name))
|
||||
GLib.idle_add(self._process_batch)
|
||||
@@ -313,110 +328,13 @@ class WallpaperSelector(Box):
|
||||
file_hash = hashlib.md5(file_name.encode("utf-8")).hexdigest()
|
||||
return os.path.join(self.CACHE_DIR, f"{file_hash}.png")
|
||||
|
||||
def on_scheme_changed(self, combo):
|
||||
scheme_id = combo.get_active_id()
|
||||
display_name = next(
|
||||
name for name, id in self.SCHEMES.items() if id == scheme_id
|
||||
)
|
||||
self.update_scheme(scheme_id)
|
||||
color_generator = get_relative_path(
|
||||
"../../../config/material-colors/generate.py"
|
||||
)
|
||||
command = f'python -O {color_generator} -R --scheme "{scheme_id}"'
|
||||
self._run_command(
|
||||
command,
|
||||
success_message=f"Applied color scheme: {display_name}",
|
||||
failure_message="Failed to apply color scheme:",
|
||||
)
|
||||
|
||||
def update_scheme(self, scheme: str):
|
||||
self._update_settings_field("generation-scheme", scheme)
|
||||
|
||||
def on_custom_color_submitted(self, entry: Entry):
|
||||
color = entry.get_text().strip()
|
||||
if not color:
|
||||
self.update_custom_color("")
|
||||
return
|
||||
if self.is_valid_hex_color(color):
|
||||
color_value = color
|
||||
elif self.is_valid_hue(color):
|
||||
color_value = self.hue_to_hex(float(color))
|
||||
else:
|
||||
return
|
||||
self.update_custom_color(color_value)
|
||||
|
||||
def on_custom_color_key_press(self, widget, event):
|
||||
if event.keyval == Gdk.KEY_Return:
|
||||
self.on_custom_color_submitted(widget)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _fetch_current_wallpaper(self):
|
||||
if not self.current_wallpaper or not os.path.exists(self.current_wallpaper):
|
||||
if os.path.exists(self.CURRENT_WALLPAPER_FILE):
|
||||
try:
|
||||
with open(self.CURRENT_WALLPAPER_FILE, "r") as f:
|
||||
self.current_wallpaper = f.read().strip()
|
||||
except Exception as e:
|
||||
print(f"Error reading current wallpaper: {e}")
|
||||
return self.current_wallpaper
|
||||
|
||||
def update_custom_color(self, color: str):
|
||||
color = color if color and color.lower() != "none" else "none"
|
||||
self._update_settings_field("custom-color", color)
|
||||
color_generator = get_relative_path(
|
||||
"../../../config/material-colors/generate.py"
|
||||
)
|
||||
if color == "none":
|
||||
scheme = self.scheme_dropdown.get_active_id()
|
||||
current_wp = self._fetch_current_wallpaper()
|
||||
command = f'python -O {color_generator} --scheme "{scheme}" --image "{current_wp}"'
|
||||
else:
|
||||
command = f'python -O {color_generator} --color "{color}"'
|
||||
self._run_command(command)
|
||||
|
||||
@staticmethod
|
||||
def _is_image(file_name: str) -> bool:
|
||||
return file_name.lower().endswith(
|
||||
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_valid_hex_color(value: str) -> bool:
|
||||
return (
|
||||
value.startswith("#")
|
||||
and len(value) == 7
|
||||
and all(c in "0123456789ABCDEFabcdef" for c in value[1:])
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_valid_hue(value: str) -> bool:
|
||||
try:
|
||||
return 0 <= float(value) <= 360
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def hue_to_hex(self, hue: float) -> str:
|
||||
r, g, b = colorsys.hls_to_rgb(hue / 360.0, 0.5, 1.0)
|
||||
return f"#{int(r * 255):02x}{int(g * 255):02x}{int(b * 255):02x}"
|
||||
|
||||
def _update_settings_field(self, field: str, value):
|
||||
try:
|
||||
with open(self.SETTINGS_FILE, "r") as f:
|
||||
settings = json.load(f)
|
||||
settings[field] = value
|
||||
with open(self.SETTINGS_FILE, "w") as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _run_command(
|
||||
self, command: str, success_message: str = None, failure_message: str = None
|
||||
):
|
||||
try:
|
||||
GLib.spawn_command_line_async(command)
|
||||
if success_message:
|
||||
print(success_message)
|
||||
except Exception:
|
||||
if failure_message:
|
||||
print(failure_message)
|
||||
def on_search_entry_focus_out(self, widget, event):
|
||||
if self.get_mapped():
|
||||
widget.grab_focus()
|
||||
return False
|
||||
Reference in new issue
Block a user