Files
Modus/modules/launcher/plugins/wallpaper.py
T

974 lines
42 KiB
Python

import colorsys
import hashlib
import json
import os
import random
import re
import threading
import time
from typing import Dict, List, Optional
from gi.repository import GdkPixbuf
from PIL import Image
import config.data as data
from fabric.utils.helpers import exec_shell_command_async
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
class WallpaperPlugin(PluginBase):
"""
Plugin for wallpaper management with search, random selection, and matugen integration.
"""
def __init__(self):
super().__init__()
self.display_name = "Wallpaper"
self.wallpapers = []
self.cache_dir = f"{data.CACHE_DIR}/thumbs"
self.thumbnail_cache: Dict[str, Optional[GdkPixbuf.Pixbuf]] = {}
self.thumbnail_loading = set() # Track which thumbnails are being loaded
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",
}
def initialize(self):
"""Initialize the wallpaper plugin."""
self.set_triggers(["wall"])
self._load_wallpapers()
os.makedirs(self.cache_dir, exist_ok=True)
# Start background thumbnail creation
self._start_background_thumbnail_creation()
def cleanup(self):
"""Cleanup the wallpaper plugin."""
pass
def _load_wallpapers(self):
"""Load available wallpapers from the wallpapers directory."""
try:
if os.path.exists(data.WALLPAPERS_DIR):
self.wallpapers = sorted(
[f for f in os.listdir(data.WALLPAPERS_DIR) if self._is_image(f)]
)
else:
print(f"Wallpapers directory not found: {data.WALLPAPERS_DIR}")
except Exception as e:
print(f"Error loading wallpapers: {e}")
def _is_image(self, filename: str) -> bool:
"""Check if file is a supported image format."""
return filename.lower().endswith(
(".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp")
)
def _get_matugen_state(self) -> bool:
"""Get current matugen state from config.json."""
self.matugen_enabled = True # Default to True
try:
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
self.matugen_enabled = config.get("matugen_enabled", True)
except FileNotFoundError:
# File doesn't exist, keep default True
pass
except Exception as e:
print(f"Error reading config file: {e}")
# Keep default True on error
return self.matugen_enabled
def _set_matugen_state(self, enabled: bool):
"""Set matugen state and save to config.json."""
self.matugen_enabled = enabled
# Save the state to config.json
try:
# Read current config
config = {}
if os.path.exists(data.CONFIG_FILE):
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
# Update matugen state
config["matugen_enabled"] = enabled
# Write back to config file
with open(data.CONFIG_FILE, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
print(f"Error writing matugen state to config: {e}")
# Clear the search query after toggling matugen
self._clear_launcher_query()
# # Send notification
# status = "enabled" if enabled else "disabled"
# exec_shell_command_async(
# f"notify-send '🎨 Matugen' 'Dynamic colors {status}' -a '{
# data.APP_NAME_CAP
# }' -e"
# )
def _clear_launcher_query(self):
"""Clear the launcher search query and reset to trigger."""
try:
# Try to access the launcher through the fabric Application
from gi.repository import GLib
from fabric import Application
app = Application.get_default()
if app and hasattr(app, "launcher"):
launcher = app.launcher
if launcher and hasattr(launcher, "search_entry"):
def clear_and_refresh():
# Clear the search entry to just the trigger
launcher.search_entry.set_text("wall ")
# Position cursor at the end
launcher.search_entry.set_position(-1)
# Trigger the search to show default wallpaper view
if hasattr(launcher, "_perform_search"):
launcher._perform_search("wall ")
return False
# Use a small delay to ensure the action completes first
GLib.timeout_add(50, clear_and_refresh)
return
# Fallback: try to find launcher instance through other means
import gc
for obj in gc.get_objects():
if hasattr(obj, "__class__") and obj.__class__.__name__ == "Launcher":
if hasattr(obj, "search_entry") and hasattr(obj, "_perform_search"):
def clear_and_refresh():
obj.search_entry.set_text("wall ")
obj.search_entry.set_position(-1)
obj._perform_search("wall ")
return False
GLib.timeout_add(50, clear_and_refresh)
return
except Exception as e:
print(f"Could not clear launcher query: {e}")
def _get_cache_path(self, filename: str) -> str:
"""Get cache path for wallpaper thumbnail."""
file_hash = hashlib.md5(filename.encode("utf-8")).hexdigest()
return os.path.join(self.cache_dir, f"{file_hash}.png")
def _create_thumbnail(self, filename: str) -> str:
"""Create thumbnail for wallpaper if it doesn't exist."""
full_path = os.path.join(data.WALLPAPERS_DIR, filename)
cache_path = self._get_cache_path(filename)
if not os.path.exists(cache_path):
try:
with Image.open(full_path) as img:
# Use faster thumbnail creation with smaller size for better performance
img.thumbnail((32, 32), Image.Resampling.LANCZOS)
img.save(cache_path, "PNG", optimize=True)
except Exception as e:
print(f"Error creating thumbnail for {filename}: {e}")
return None
return cache_path
def _start_background_thumbnail_creation(self):
"""Start background thread to create thumbnails for all wallpapers."""
def create_thumbnails():
for wallpaper in self.wallpapers:
if wallpaper not in self.thumbnail_loading:
self.thumbnail_loading.add(wallpaper)
try:
cache_path = self._create_thumbnail(wallpaper)
if cache_path and os.path.exists(cache_path):
# Load thumbnail into memory cache
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
cache_path, 32, 32, True
)
self.thumbnail_cache[wallpaper] = pixbuf
except Exception as e:
print(f"Error loading thumbnail for {wallpaper}: {e}")
self.thumbnail_cache[wallpaper] = None
else:
self.thumbnail_cache[wallpaper] = None
except Exception as e:
print(f"Error processing thumbnail for {wallpaper}: {e}")
self.thumbnail_cache[wallpaper] = None
finally:
self.thumbnail_loading.discard(wallpaper)
# Small delay to prevent overwhelming the system
time.sleep(0.01)
# Start background thread
thread = threading.Thread(target=create_thumbnails, daemon=True)
thread.start()
def _get_thumbnail_fast(self, filename: str) -> Optional[GdkPixbuf.Pixbuf]:
"""Get thumbnail quickly from cache or return None if not ready."""
# Return cached thumbnail if available
if filename in self.thumbnail_cache:
return self.thumbnail_cache[filename]
# Check if thumbnail file exists and load it immediately
cache_path = self._get_cache_path(filename)
if os.path.exists(cache_path):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
cache_path, 32, 32, True
)
self.thumbnail_cache[filename] = pixbuf
return pixbuf
except Exception as e:
print(f"Error loading thumbnail for {filename}: {e}")
self.thumbnail_cache[filename] = None
return None
# If not in cache and file doesn't exist, trigger background creation
if filename not in self.thumbnail_loading:
self.thumbnail_loading.add(filename)
def create_async():
try:
cache_path = self._create_thumbnail(filename)
if cache_path and os.path.exists(cache_path):
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
cache_path, 32, 32, True
)
self.thumbnail_cache[filename] = pixbuf
except Exception as e:
print(f"Error loading thumbnail for {filename}: {e}")
self.thumbnail_cache[filename] = None
else:
self.thumbnail_cache[filename] = None
except Exception as e:
print(f"Error creating thumbnail for {filename}: {e}")
self.thumbnail_cache[filename] = None
finally:
self.thumbnail_loading.discard(filename)
thread = threading.Thread(target=create_async, daemon=True)
thread.start()
return None
def _set_wallpaper(self, filename: str, scheme: str = None):
"""Set wallpaper and apply matugen color scheme if enabled."""
full_path = os.path.join(data.WALLPAPERS_DIR, filename)
current_wall = os.path.expanduser("~/.current.wall")
if scheme is None:
scheme = self._get_current_scheme()
# Update current wallpaper symlink
if os.path.isfile(current_wall) or os.path.islink(current_wall):
os.remove(current_wall)
os.symlink(full_path, current_wall)
# Always set the wallpaper image
exec_shell_command_async(
f'swww img "{
full_path
}" -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest'
)
# If Matugen is enabled, also apply the color scheme
matugen_enabled = self._get_matugen_state()
if matugen_enabled:
exec_shell_command_async(f'matugen image "{full_path}" -t {scheme}')
def _set_random_wallpaper(self):
"""Set a random wallpaper."""
if not self.wallpapers:
return
filename = random.choice(self.wallpapers)
self._set_wallpaper(filename)
# Show notification for immediate feedback
exec_shell_command_async(
f"notify-send '🎲 Random Wallpaper' 'Applied: {filename}' -a '{
data.APP_NAME_CAP
}' -e"
)
return filename
def _hsl_to_rgb_hex(self, h: float, s: float = 1.0, l: float = 0.5) -> str:
"""Convert HSL color value to RGB HEX string."""
# colorsys uses HLS, not HSL, and expects values between 0.0 and 1.0
hue = h / 360.0
r, g, b = colorsys.hls_to_rgb(hue, l, s) # Note the order: H, L, S
r_int, g_int, b_int = int(r * 255), int(g * 255), int(b * 255)
return f"#{r_int:02X}{g_int:02X}{b_int:02X}"
def _is_valid_hex_color(self, hex_color: str) -> bool:
"""Check if string is a valid hex color."""
if not hex_color.startswith("#"):
hex_color = "#" + hex_color
return bool(re.match(r"^#[0-9A-Fa-f]{6}$", hex_color))
def _get_current_scheme(self) -> str:
"""Get current color scheme from config (default to tonal-spot)."""
try:
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
return config.get("current_scheme", "scheme-tonal-spot")
except FileNotFoundError:
# File doesn't exist, return default
return "scheme-tonal-spot"
except Exception as e:
print(f"Error reading current scheme from config: {e}")
return "scheme-tonal-spot"
def _set_current_scheme(self, scheme: str):
"""Set current color scheme and apply it."""
scheme_name = self.schemes.get(scheme, scheme)
matugen_enabled = self._get_matugen_state()
# Save the scheme to config
try:
# Read current config
config = {}
if os.path.exists(data.CONFIG_FILE):
with open(data.CONFIG_FILE, "r") as f:
config = json.load(f)
# Update current scheme
config["current_scheme"] = scheme
# Write back to config file
with open(data.CONFIG_FILE, "w") as f:
json.dump(config, f, indent=4)
except Exception as e:
print(f"Error saving current scheme to config: {e}")
return
# Apply the scheme to current wallpaper if matugen is enabled
if matugen_enabled:
current_wall = os.path.expanduser("~/.current.wall")
if os.path.exists(current_wall) and os.path.islink(current_wall):
# Get the current wallpaper path
wallpaper_path = os.readlink(current_wall)
if os.path.exists(wallpaper_path):
# Apply the new scheme to current wallpaper
exec_shell_command_async(
f'matugen image "{wallpaper_path}" -t {scheme}'
)
# Send notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Applied {
scheme_name
} scheme' -a '{data.APP_NAME_CAP}' -e"
)
else:
# No current wallpaper, just show scheme change notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {
scheme_name
} (will apply to next wallpaper)' -a '{data.APP_NAME_CAP}' -e"
)
else:
# No current wallpaper, just show scheme change notification
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {
scheme_name
} (will apply to next wallpaper)' -a '{data.APP_NAME_CAP}' -e"
)
else:
# Matugen is disabled, just save the setting
exec_shell_command_async(
f"notify-send '🎨 Color Scheme' 'Set to {
scheme_name
} (matugen disabled)' -a '{data.APP_NAME_CAP}' -e"
)
def _apply_hex_color(self, hex_color: str, scheme: str = None):
"""Apply hex color using matugen. Assumes matugen is enabled."""
if not hex_color.startswith("#"):
hex_color = "#" + hex_color
if scheme is None:
scheme = self._get_current_scheme()
exec_shell_command_async(f'matugen color hex "{hex_color}" -t {scheme}')
def _apply_hex_color_direct(self, hex_color: str, scheme: str = None):
"""Apply hex color using matugen (following example_wallpapers.py pattern)."""
if not hex_color.startswith("#"):
hex_color = "#" + hex_color
if scheme is None:
scheme = self._get_current_scheme()
exec_shell_command_async(f'matugen color hex "{hex_color}" -t {scheme}')
# Send notification
exec_shell_command_async(
f"notify-send '🎨 Hex Color Applied' 'Applied color: {hex_color}' -a '{
data.APP_NAME_CAP
}' -e"
)
def _generate_random_hex_color(self) -> str:
"""Generate a random hex color."""
hue = random.randint(0, 360)
return self._hsl_to_rgb_hex(hue)
def _get_status_indicators(self) -> tuple:
"""Get current status indicators for display."""
current_scheme = self._get_current_scheme()
matugen_enabled = self._get_matugen_state()
scheme_name = self.schemes.get(current_scheme, current_scheme)
indicators = []
if not matugen_enabled:
indicators.append("Matugen Off")
indicator_text = " • " + " • ".join(indicators) if indicators else ""
status_text = f"Matugen: {
'Enabled' if matugen_enabled else 'Disabled'
} • Scheme: {scheme_name}"
return indicator_text, status_text, current_scheme, matugen_enabled
def query(self, query_string: str) -> List[Result]:
"""Search wallpapers and provide management options."""
results = []
query = query_string.lower().strip()
# Get status indicators for consistent display
indicator_text, status_text, current_scheme, matugen_enabled = (
self._get_status_indicators()
)
# Special commands
if query.strip() == "random":
# Show result for random wallpaper (execute on Enter)
results.append(
Result(
title=f"Random Wallpaper{indicator_text}",
subtitle=f"Set a random wallpaper • {status_text}",
icon_name="media-playlist-shuffle-symbolic",
action=lambda: self._set_random_wallpaper(),
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "random",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif query.startswith("random") and query.strip() != "random":
# Show suggestion for random wallpaper (partial match)
results.append(
Result(
title=f"Random Wallpaper{indicator_text}",
subtitle=f"Set a random wallpaper • {status_text}",
icon_name="media-playlist-shuffle-symbolic",
action=lambda: self._set_random_wallpaper(),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "random_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Hex color commands
if "color" in query or "hex" in query or query.startswith("#"):
# Check for scheme specification in the query
scheme = self._get_current_scheme()
for scheme_id, scheme_name in self.schemes.items():
if (
scheme_name.lower() in query.lower()
or scheme_id.lower() in query.lower()
):
scheme = scheme_id
break
# Check for hex color patterns
hex_match = re.search(r"#?([0-9A-Fa-f]{6})", query)
if hex_match:
hex_color = "#" + hex_match.group(1)
scheme_name = self.schemes.get(scheme, scheme)
# Check if this is a complete hex color input (6 digits)
# Execute immediately when we have a complete 6-digit hex color
if len(hex_match.group(1)) == 6:
# Check if there's additional text after the hex color
hex_end_pos = hex_match.end()
remaining_text = query[hex_end_pos:].strip()
# Only show result if no additional text after hex color (exact match)
if not remaining_text:
# Hex colors work when matugen is OFF (following example_wallpapers.py pattern)
if not matugen_enabled:
# Show result for hex color (execute on Enter)
results.append(
Result(
title=f"Apply Hex Color: {hex_color}{
indicator_text
}",
subtitle=f"Apply with {scheme_name} scheme • {
status_text
}",
icon_name="color-picker-symbolic",
action=lambda c=hex_color, s=scheme: self._apply_hex_color_direct(
c, s
),
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "hex_color",
"color": hex_color,
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show error result when matugen is enabled
results.append(
Result(
title=f"Cannot Apply Hex Color: {hex_color}{
indicator_text
}",
subtitle="Matugen is enabled • Disable matugen to use hex colors",
icon_name="color-picker-symbolic",
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "hex_color_failed",
"color": hex_color,
"bypass_max_results": True,
},
)
)
else:
# Show suggestion for hex color with additional text (partial match)
results.append(
Result(
title=f"Apply Hex Color: {hex_color}{indicator_text}",
subtitle=f"Apply with {scheme_name} scheme • {
status_text
}",
icon_name="color-picker-symbolic",
action=lambda c=hex_color, s=scheme: (
self._apply_hex_color_direct(c, s)
if not matugen_enabled
else None
),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "hex_color_suggestion",
"color": hex_color,
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Incomplete hex color, show as suggestion
results.append(
Result(
title=f"Hex Color (incomplete): {hex_color}",
subtitle="Complete the 6-digit hex color to apply",
icon_name="color-picker-symbolic",
action=lambda: None,
relevance=0.7,
plugin_name=self.display_name,
data={
"action": "hex_color_incomplete",
"color": hex_color,
"bypass_max_results": True,
},
)
)
elif "random" in query and ("color" in query or "hex" in query):
# Check for exact matches for random color commands
if (
query.strip() == "color random"
or query.strip() == "hex random"
or query.strip() == "random color"
or query.strip() == "random hex"
):
# Random hex color - show result (execute on Enter)
scheme_name = self.schemes.get(scheme, scheme)
# Check if matugen is disabled (hex colors work when matugen is OFF)
if not matugen_enabled:
results.append(
Result(
title=f"Random Hex Color{indicator_text}",
subtitle=f"Generate and apply random color with {
scheme_name
} scheme • {status_text}",
icon_name="color-picker-symbolic",
action=lambda s=scheme: self._apply_hex_color_direct(
self._generate_random_hex_color(), s
),
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "random_hex",
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show error result when matugen is enabled
results.append(
Result(
title=f"Cannot Apply Random Color{indicator_text}",
subtitle="Matugen is enabled • Disable matugen to use hex colors",
icon_name="color-picker-symbolic",
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={
"action": "random_hex_failed",
"bypass_max_results": True,
},
)
)
else:
# Show suggestion for random hex color (partial match)
results.append(
Result(
title=f"Random Hex Color{indicator_text}",
subtitle=f"Generate and apply random color • {status_text}",
icon_name="color-picker-symbolic",
action=lambda s=scheme: (
self._apply_hex_color_direct(
self._generate_random_hex_color(), s
)
if not matugen_enabled
else None
),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "random_hex_suggestion",
"scheme": scheme,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show hex color help
results.append(
Result(
title="Hex Color Commands",
subtitle="Use: color #FF5733, hex #00FF00, color random",
icon_name="color-picker-symbolic",
action=lambda: None,
relevance=0.8,
plugin_name=self.display_name,
data={"action": "hex_help", "bypass_max_results": True},
)
)
# Color scheme commands
if "scheme" in query:
current_scheme = self._get_current_scheme()
matugen_enabled = self._get_matugen_state()
# Show all available schemes
for scheme_id, scheme_name in self.schemes.items():
# Check if this scheme matches the query (for filtering)
if query.strip() == "scheme":
# Show all schemes when just "scheme" is typed
scheme_matches = True
else:
# Extract search terms from query (remove "scheme" keyword)
search_terms = query.replace("scheme", "").strip().split()
scheme_matches = False
# Check if any search term matches the scheme name or ID
for term in search_terms:
if (
term.lower() in scheme_name.lower()
or term.lower() in scheme_id.lower()
):
scheme_matches = True
break
if scheme_matches:
# Create indicators
indicators = []
if scheme_id == current_scheme:
indicators.append("Current")
if not matugen_enabled:
indicators.append("Matugen Off")
indicator_text = (
" • " + " • ".join(indicators) if indicators else ""
)
results.append(
Result(
title=f"{scheme_name}{indicator_text}",
subtitle=f"Set color scheme to {scheme_name}"
+ (
" (requires matugen enabled)"
if not matugen_enabled
else ""
),
icon_name="color-management-symbolic",
action=lambda s=scheme_id: self._set_current_scheme(s),
relevance=1.0 if scheme_id == current_scheme else 0.8,
plugin_name=self.display_name,
data={
"action": "scheme_select",
"scheme": scheme_id,
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Matugen controls
if "matugen" in query:
current_state = self._get_matugen_state()
# Check for exact command matches
if query.strip() == "matugen on" or query.strip() == "matugen enable":
# Show result for enabling matugen (execute on Enter)
results.append(
Result(
title=f"Enable Matugen{indicator_text}",
subtitle=f"Turn on dynamic color generation • {status_text}",
icon_name="color-management-symbolic",
action=lambda: self._set_matugen_state(True),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "matugen_on",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif query.strip() == "matugen off" or query.strip() == "matugen disable":
# Show result for disabling matugen (execute on Enter)
results.append(
Result(
title=f"Disable Matugen{indicator_text}",
subtitle=f"Turn off dynamic color generation • {status_text}",
icon_name="color-management-symbolic",
action=lambda: self._set_matugen_state(False),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "matugen_off",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif query.strip() == "matugen toggle":
# Show result for toggling matugen (execute on Enter)
new_state = not current_state
results.append(
Result(
title=f"Toggle Matugen to {
'Enabled' if new_state else 'Disabled'
}{indicator_text}",
subtitle=f"Switch matugen to {
'enabled' if new_state else 'disabled'
} • {status_text}",
icon_name="color-management-symbolic",
action=lambda: self._set_matugen_state(new_state),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "matugen_toggle",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif ("on" in query or "enable" in query) and not query.strip().endswith(
("on", "enable")
):
# Show suggestion for enabling (partial match)
results.append(
Result(
title=f"Enable Matugen{indicator_text}",
subtitle=f"Turn on dynamic color generation • {status_text}",
icon_name="color-management-symbolic",
action=lambda: self._set_matugen_state(True),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "matugen_on_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif ("off" in query or "disable" in query) and not query.strip().endswith(
("off", "disable")
):
# Show suggestion for disabling (partial match)
results.append(
Result(
title=f"Disable Matugen{indicator_text}",
subtitle=f"Turn off dynamic color generation • {status_text}",
icon_name="color-management-symbolic",
action=lambda: self._set_matugen_state(False),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "matugen_off_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
elif "toggle" in query and not query.strip().endswith("toggle"):
# Show suggestion for toggling (partial match)
results.append(
Result(
title=f"Toggle Matugen{indicator_text}",
subtitle=f"Switch matugen state • {status_text}",
icon_name="color-management-symbolic",
action=lambda: self._set_matugen_state(not current_state),
relevance=0.8,
plugin_name=self.display_name,
data={
"action": "matugen_toggle_suggestion",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
else:
# Show current state with scheme info
results.append(
Result(
title=f"Matugen: {'Enabled' if current_state else 'Disabled'}{
indicator_text
}",
subtitle=f"Dynamic colors • {status_text}",
icon_name="color-management-symbolic",
action=lambda: None,
relevance=0.8,
plugin_name=self.display_name,
data={"action": "matugen_status", "bypass_max_results": True},
)
)
# Status command
if query == "status" or query == "info":
results.append(
Result(
title=f"Wallpaper System Status{indicator_text}",
subtitle=status_text,
icon_name="color-management-symbolic",
action=lambda: None,
relevance=1.0,
plugin_name=self.display_name,
data={"action": "status", "bypass_max_results": True},
)
)
# Show quick actions
results.append(
Result(
title=f"Random Wallpaper{indicator_text}",
subtitle=f"Set a random wallpaper • {status_text}",
icon_name="media-playlist-shuffle-symbolic",
action=lambda: self._set_random_wallpaper(),
relevance=0.9,
plugin_name=self.display_name,
data={
"action": "random_quick",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Search wallpapers by filename - Show ALL wallpapers like example_wallpapers.py
if not query or (
query
and "matugen" not in query
and "random" not in query
and "scheme" not in query
and "status" not in query
and "info" not in query
and "color" not in query
and "hex" not in query
):
matching_wallpapers = []
for wallpaper in self.wallpapers:
if not query or query in wallpaper.lower():
# Calculate relevance
relevance = 1.0 if query == wallpaper.lower() else 0.7
if query and query in wallpaper.lower():
relevance = 0.8
matching_wallpapers.append((wallpaper, relevance))
# Sort by relevance and show ALL wallpapers (like example_wallpapers.py)
matching_wallpapers.sort(key=lambda x: x[1], reverse=True)
# Show ALL wallpapers instead of limiting (following example_wallpapers.py pattern)
for wallpaper, relevance in matching_wallpapers:
# Use fast thumbnail loading
icon = self._get_thumbnail_fast(wallpaper)
results.append(
Result(
title=f"{wallpaper}{indicator_text if not query else ''}",
subtitle=f"Set as wallpaper{
' • ' + status_text if not query else ''
}",
icon=icon,
icon_name="image-x-generic-symbolic" if not icon else None,
action=lambda w=wallpaper: self._set_wallpaper(w),
relevance=relevance,
plugin_name=self.display_name,
data={
"wallpaper": wallpaper,
"action": "set",
"bypass_max_results": True,
"keep_launcher_open": True,
},
)
)
# Sort by relevance - no limit since we use bypass_max_results
results.sort(key=lambda x: x.relevance, reverse=True)
return results