From fee0aef45a6047feb9e54f22e96bca84c009b862 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 18 Jun 2025 17:13:18 +0545 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=85=20style(colors):=20update=20color?= =?UTF-8?q?=20scheme=20in=20config/hypr/colors.conf=20and=20styles/colors.?= =?UTF-8?q?css?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/hypr/colors.conf | 22 +- config/launcher.json | 40 +- modules/launcher/main.py | 63 ++- modules/launcher/plugin_manager.py | 1 - .../launcher/plugins/{ => archive}/files.py | 171 +++++--- modules/launcher/plugins/kanban.py | 2 +- modules/launcher/plugins/wallpaper.py | 415 +++++++++++++----- modules/launcher/trigger_config.py | 106 +---- styles/colors.css | 48 +- 9 files changed, 532 insertions(+), 336 deletions(-) rename modules/launcher/plugins/{ => archive}/files.py (60%) diff --git a/config/hypr/colors.conf b/config/hypr/colors.conf index 0721802..3319508 100644 --- a/config/hypr/colors.conf +++ b/config/hypr/colors.conf @@ -1,13 +1,13 @@ -$wallpaper = /home/sankalp/Pictures/wallpapers/155.jpg -$background = 101418 -$foreground = e0e2e8 +$wallpaper = /home/sankalp/Pictures/wallpapers/169.jpg +$background = 121318 +$foreground = e3e2e9 -$primary = 99ccfa -$secondary = b8c8da -$tertiary = d2bfe7 -$surface = 101418 -$surface_bright = 36393e -$outline = 8c9198 +$primary = b4c5ff +$secondary = c1c5dd +$tertiary = e2bbdb +$surface = 121318 +$surface_bright = 38393f +$outline = 8f909a $error = ff5945 $shadow = 000000 @@ -15,13 +15,13 @@ $shadow = 000000 $red = ffb2b9 $green = 95d5a7 $yellow = b8cf84 -$blue = afc6ff +$blue = b9c3ff $magenta = e4b7f3 $cyan = 82d3e2 $white = 82d3e0 $red_dim = ff4c5c $green_dim = 4cb86a $yellow_dim = 8baa43 -$blue_dim = 497dff +$blue_dim = 536cff $magenta_dim = c360e4 $cyan_dim = 30b5ce diff --git a/config/launcher.json b/config/launcher.json index 1ac2dba..9b27e12 100644 --- a/config/launcher.json +++ b/config/launcher.json @@ -25,14 +25,6 @@ "icon": "apps", "description": "Applications - Launch installed applications" }, - "file": { - "icon": "file", - "description": "Files - Search for files and documents" - }, - "find": { - "icon": "file", - "description": "Find - Locate files and folders" - }, "bin": { "icon": "terminal", "description": "Bins - Search and run executable binaries" @@ -71,6 +63,38 @@ "icon": "world", "description": "Quick Web Search - Fast web search with question mark" }, + "google": { + "icon": "google", + "description": "Google Search - Search with Google" + }, + "reddit": { + "icon": "reddit", + "description": "Reddit Search - Search discussions on Reddit" + }, + "youtube": { + "icon": "youtube", + "description": "YouTube Search - Search videos on YouTube" + }, + "github": { + "icon": "github", + "description": "GitHub Search - Search repositories on GitHub" + }, + "duckduckgo": { + "icon": "duckduckgo", + "description": "DuckDuckGo Search - Privacy-focused search" + }, + "stackoverflow": { + "icon": "stackoverflow", + "description": "Stack Overflow Search - Programming questions" + }, + "wikipedia": { + "icon": "wikipedia", + "description": "Wikipedia Search - Search articles on Wikipedia" + }, + "linkedin": { + "icon": "linkedin", + "description": "LinkedIn Search - Search professionals and jobs" + }, "kanban": { "icon": "kanban", "description": "Kanban - Task management with kanban-style columns" diff --git a/modules/launcher/main.py b/modules/launcher/main.py index 95aac5a..0ffd4d9 100644 --- a/modules/launcher/main.py +++ b/modules/launcher/main.py @@ -219,19 +219,13 @@ class Launcher(Window): print(f"Error in triggered plugin {triggered_plugin.name}: {e}") all_results = [] else: - # No trigger detected - try global search across all plugins + # No trigger detected - only show trigger suggestions self.triggered_plugin = None self.active_trigger = "" - # First try trigger suggestions if query matches trigger prefixes + # Show trigger suggestions if query matches trigger prefixes trigger_suggestions = self._get_trigger_suggestions(query) - - if trigger_suggestions: - # Show trigger suggestions - all_results = trigger_suggestions - else: - # No trigger suggestions - do global search across all plugins - all_results = self._global_search(query) + all_results = trigger_suggestions # Sort results by relevance score all_results.sort(key=lambda r: r.relevance, reverse=True) @@ -294,27 +288,7 @@ class Launcher(Window): return "" - def _global_search(self, query: str): - """ - Perform global search across all plugins. - Args: - query: The search query - - Returns: - List of Result objects from all plugins - """ - all_results = [] - - # Search in all active plugins - for plugin in self.plugin_manager.get_active_plugins(): - try: - plugin_results = plugin.query(query) - all_results.extend(plugin_results) - except Exception as e: - print(f"Error searching in plugin {plugin.name}: {e}") - - return all_results def _show_available_triggers(self): """Show available triggers when launcher is first opened.""" @@ -373,7 +347,7 @@ class Launcher(Window): } # Get max examples to show from configuration - max_examples = self.trigger_config.get_max_examples_shown() + max_examples = self.trigger_config.settings.get("max_examples_shown", 2) # Show trigger suggestions based on query if query_lower: @@ -577,6 +551,23 @@ class Launcher(Window): self.close_launcher() return True + # Backspace - exit trigger mode if at trigger boundary + if keyval == Gdk.KEY_BackSpace: + if self.triggered_plugin: + current_text = self.search_entry.get_text() + trigger_text = self.active_trigger.strip() + + # Check if we're at the trigger boundary (only trigger text or trigger + space) + if current_text == trigger_text or current_text == f"{trigger_text} ": + # Exit trigger mode + self.triggered_plugin = None + self.active_trigger = "" + self.search_entry.set_text("") + self._clear_results() + return True + # Let normal backspace behavior continue for other cases + return False + # Up/Down - navigate results if keyval == Gdk.KEY_Up: if self.results: @@ -750,19 +741,21 @@ class Launcher(Window): # The widget handles its own interactions return - # Check if this is a trigger suggestion + # Check if this is a trigger suggestion or should keep launcher open is_trigger_suggestion = ( result.data and result.data.get("type") == "trigger_suggestion" ) + keep_launcher_open = ( + result.data and result.data.get("keep_launcher_open", False) + ) # Activate the result result.activate() - # Only hide launcher for non-trigger suggestions - if not is_trigger_suggestion: + # Only hide launcher if it's not a trigger suggestion and doesn't have keep_launcher_open flag + if not is_trigger_suggestion and not keep_launcher_open: self.close_launcher() - # For trigger suggestions, the launcher stays open and - # the user can continue typing after the trigger + # For trigger suggestions and keep_launcher_open actions, the launcher stays open except Exception as e: print(f"Error activating result: {e}") diff --git a/modules/launcher/plugin_manager.py b/modules/launcher/plugin_manager.py index 125a2bf..788379f 100644 --- a/modules/launcher/plugin_manager.py +++ b/modules/launcher/plugin_manager.py @@ -83,7 +83,6 @@ class PluginManager: """Activate default plugins.""" default_plugins = [ "applications", - "files", "calculator", "system", "clipboard", diff --git a/modules/launcher/plugins/files.py b/modules/launcher/plugins/archive/files.py similarity index 60% rename from modules/launcher/plugins/files.py rename to modules/launcher/plugins/archive/files.py index d2e145c..a118f30 100644 --- a/modules/launcher/plugins/files.py +++ b/modules/launcher/plugins/archive/files.py @@ -1,6 +1,6 @@ """ Files plugin for the launcher. -High-performance file and directory search using advanced fuzzy matching. +High-performance file search (files only, excludes directories) using advanced fuzzy matching. """ import os @@ -11,40 +11,45 @@ from typing import List, Dict, Set, Tuple from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed from thefuzz import fuzz -from ..plugin_base import PluginBase -from ..result import Result +from modules.launcher.plugin_base import PluginBase +from modules.launcher.result import Result import utils.icons as icons class FilesPlugin(PluginBase): """ - High-performance plugin for searching files and directories. + High-performance plugin for searching files only (excludes directories). Uses advanced fuzzy matching, caching, and threading for optimal performance. """ def __init__(self): super().__init__() self.display_name = "Files" - self.description = "Search for files and directories" + self.description = "Search for files only" - # Performance-optimized search paths + # Performance-optimized search paths - focus on user directories self.search_paths = [ - os.path.expanduser("~"), - os.path.expanduser("/"), + os.path.expanduser("~/Documents"), + os.path.expanduser("~/Downloads"), + os.path.expanduser("~/Desktop"), + os.path.expanduser("~/Pictures"), + os.path.expanduser("~/Videos"), + os.path.expanduser("~/Music"), + os.path.expanduser("~"), # Home directory but with limited depth ] # Performance settings self.max_results = 15 - self.max_depth = 3 - self.search_timeout = 0.8 - self.min_query_length = 1 + self.max_depth = 2 # Reduced depth for better performance + self.search_timeout = 0.5 # Reduced timeout + self.min_query_length = 1 # Allow single character searches - # Fuzzy search settings + # Fuzzy search settings - optimized for performance self.fuzzy_thresholds = { - "ratio": 60, - "partial": 70, - "token_sort": 65, - "token_set": 65, + "ratio": 70, # Increased for better precision + "partial": 80, # Increased for better precision + "token_sort": 75, # Increased for better precision + "token_set": 75, # Increased for better precision } # Enhanced caching @@ -89,10 +94,9 @@ class FilesPlugin(PluginBase): if query.startswith("/") or query.startswith("~"): return self._handle_path_query(query) - # Check if this is a file-only search - is_file_only = query.startswith("file ") - if is_file_only: - query = query[5:].strip() + # Always search for files only since this is the files plugin + # triggered by "file" or "find" keywords + is_file_only = True # Use cached search for better performance results = self._fast_search(query, file_only=is_file_only) @@ -135,32 +139,44 @@ class FilesPlugin(PluginBase): return [] def _fast_search(self, query: str, file_only: bool = False) -> List[Result]: - """Fast search using cached file lists and concurrent processing.""" + """Fast search using cached file lists and optimized matching.""" results = [] query_lower = query.lower() - # Search in cached directories + # Early exit for empty queries + if len(query_lower) < self.min_query_length: + return results + + # Search in cached directories with early termination for search_path in self.search_paths: if not os.path.exists(search_path): continue + # Skip home directory if we already have enough results from specific folders + if search_path == os.path.expanduser("~") and len(results) >= self.max_results: + continue + cached_files = self._get_cached_files(search_path) for file_path in cached_files: if file_only and os.path.isdir(file_path): continue filename = os.path.basename(file_path) - if self._simple_fuzzy_match(query_lower, filename): - relevance = self._calculate_relevance(filename, query_lower) - result = self._create_file_result(file_path, relevance) - if result: - results.append(result) - if len(results) >= self.max_results * 2: - break + # Fast pre-filter: check if query is in filename before expensive fuzzy matching + if query_lower not in filename.lower(): + # Only do fuzzy matching if simple substring match fails + if not self._simple_fuzzy_match(query_lower, filename): + continue - if len(results) >= self.max_results * 2: - break + relevance = self._calculate_relevance(filename, query_lower) + result = self._create_file_result(file_path, relevance) + if result: + results.append(result) + + # Early termination when we have enough results + if len(results) >= self.max_results * 3: + return results return results @@ -183,12 +199,13 @@ class FilesPlugin(PluginBase): return files def _scan_directory_fast(self, directory: str) -> List[str]: - """Fast directory scan with improved depth and fuzzy search.""" + """Fast directory scan with optimized depth and early termination.""" files = [] current_depth = 0 + max_files_per_dir = 1000 # Limit files per directory to prevent memory issues def scan_dir(path: str, depth: int): - if depth > self.max_depth: + if depth > self.max_depth or len(files) >= max_files_per_dir: return try: @@ -198,10 +215,29 @@ class FilesPlugin(PluginBase): if entry.name.startswith("."): continue + # Skip common large directories that are unlikely to contain user files + if entry.is_dir() and entry.name in { + "node_modules", ".git", ".cache", "__pycache__", + ".npm", ".cargo", ".rustup", "target", "build", + ".vscode", ".idea", "venv", ".env" + }: + continue + files.append(entry.path) - if entry.is_dir(): - scan_dir(entry.path, depth + 1) + # Early termination if we have too many files + if len(files) >= max_files_per_dir: + return + + # Only recurse into directories if we're not at max depth + # and for home directory, be more selective + if entry.is_dir() and depth < self.max_depth: + if path == os.path.expanduser("~"): + # For home directory, only scan important subdirectories + if self._is_important_directory(entry.name): + scan_dir(entry.path, depth + 1) + else: + scan_dir(entry.path, depth + 1) except (PermissionError, OSError): continue @@ -309,42 +345,55 @@ class FilesPlugin(PluginBase): self._open_path(dir_path) def _simple_fuzzy_match(self, query: str, text: str) -> bool: - """Advanced fuzzy matching using multiple methods.""" + """Optimized fuzzy matching with performance improvements.""" text_lower = text.lower() query_lower = query.lower() + # Fast exact substring match if query_lower in text_lower: return True - return ( - fuzz.ratio(query_lower, text_lower) >= self.fuzzy_thresholds["ratio"] - or fuzz.partial_ratio(query_lower, text_lower) - >= self.fuzzy_thresholds["partial"] - or fuzz.token_sort_ratio(query_lower, text_lower) - >= self.fuzzy_thresholds["token_sort"] - or fuzz.token_set_ratio(query_lower, text_lower) - >= self.fuzzy_thresholds["token_set"] - ) + # Fast prefix match + if text_lower.startswith(query_lower): + return True + + # Only do expensive fuzzy matching for longer queries + if len(query_lower) >= 3: + # Use only the most effective fuzzy matching methods + return ( + fuzz.partial_ratio(query_lower, text_lower) >= self.fuzzy_thresholds["partial"] + or fuzz.ratio(query_lower, text_lower) >= self.fuzzy_thresholds["ratio"] + ) + + return False def _calculate_relevance(self, filename: str, query: str) -> float: - """Calculate relevance score using multiple fuzzy matching methods.""" + """Calculate relevance score with optimized performance.""" filename_lower = filename.lower() query_lower = query.lower() - ratio = fuzz.ratio(query_lower, filename_lower) - partial = fuzz.partial_ratio(query_lower, filename_lower) - token_sort = fuzz.token_sort_ratio(query_lower, filename_lower) - token_set = fuzz.token_set_ratio(query_lower, filename_lower) - - relevance = ( - ratio * 0.2 + partial * 0.4 + token_sort * 0.2 + token_set * 0.2 - ) / 100.0 - + # Fast exact match if query_lower == filename_lower: - relevance = 1.0 - elif filename_lower.startswith(query_lower): - relevance = max(relevance, 0.9) - elif query_lower in filename_lower: - relevance = max(relevance, 0.8) + return 1.0 - return relevance + # Fast prefix match + if filename_lower.startswith(query_lower): + return 0.95 + + # Fast substring match + if query_lower in filename_lower: + # Calculate position-based relevance + position = filename_lower.index(query_lower) + position_score = 1.0 - (position / len(filename_lower)) + return 0.8 + (position_score * 0.1) + + # Only do expensive fuzzy matching for longer queries + if len(query_lower) >= 3: + partial = fuzz.partial_ratio(query_lower, filename_lower) + ratio = fuzz.ratio(query_lower, filename_lower) + + # Simplified relevance calculation + relevance = (partial * 0.6 + ratio * 0.4) / 100.0 + return max(relevance, 0.3) # Minimum relevance for fuzzy matches + + return 0.3 # Default relevance for short queries diff --git a/modules/launcher/plugins/kanban.py b/modules/launcher/plugins/kanban.py index e05787c..4910f9d 100644 --- a/modules/launcher/plugins/kanban.py +++ b/modules/launcher/plugins/kanban.py @@ -431,7 +431,7 @@ class KanbanPlugin(PluginBase): def initialize(self): """Initialize the Kanban plugin.""" - self.set_triggers(["todo", "kanban", "task"]) + self.set_triggers(["kanban" "kanban ",]) self.description = "Kanban board for task management. Use 'kanban add ' to quickly add tasks." def cleanup(self): diff --git a/modules/launcher/plugins/wallpaper.py b/modules/launcher/plugins/wallpaper.py index f48d4b8..2c3bb63 100644 --- a/modules/launcher/plugins/wallpaper.py +++ b/modules/launcher/plugins/wallpaper.py @@ -8,7 +8,8 @@ import random import hashlib import colorsys import re -from typing import List, Dict +import json +from typing import List from PIL import Image from gi.repository import GdkPixbuf from modules.launcher.plugin_base import PluginBase @@ -25,6 +26,7 @@ class WallpaperPlugin(PluginBase): def __init__(self): super().__init__() + self.display_name = "Wallpaper" self.wallpapers = [] self.cache_dir = f"{data.CACHE_DIR}/thumbs" self.schemes = { @@ -67,32 +69,50 @@ class WallpaperPlugin(PluginBase): ) def _get_matugen_state(self) -> bool: - """Get current matugen state from file.""" + """Get current matugen state from config.json.""" + self.matugen_enabled = True # Default to True try: - if os.path.exists(data.MATUGEN_STATE_FILE): - with open(data.MATUGEN_STATE_FILE, "r") as f: - content = f.read().strip().lower() - return content == "true" - return True # Default to True + 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 matugen state: {e}") - return True + 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 to file.""" + """Set matugen state and save to config.json.""" + self.matugen_enabled = enabled + + # Save the state to config.json try: - os.makedirs(os.path.dirname(data.MATUGEN_STATE_FILE), exist_ok=True) - with open(data.MATUGEN_STATE_FILE, "w") as f: - f.write("true" if enabled else "false") - # Show notification for immediate feedback - status = "enabled" if enabled else "disabled" - exec_shell_command_async( - f"notify-send '🎨 Matugen' 'Dynamic colors {status}' -a '{ - data.APP_NAME_CAP - }' -e" - ) + # 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 setting matugen state: {e}") + print(f"Error writing matugen state to config: {e}") + + # 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 _get_cache_path(self, filename: str) -> str: """Get cache path for wallpaper thumbnail.""" @@ -133,12 +153,23 @@ class WallpaperPlugin(PluginBase): matugen_enabled = self._get_matugen_state() if matugen_enabled: exec_shell_command_async(f'matugen image "{full_path}" -t {scheme}') + scheme_name = self.schemes.get(scheme, scheme) + exec_shell_command_async( + f"notify-send '🖼️ Wallpaper Applied' 'File: {filename}\\nScheme: {scheme_name}' -a '{ + data.APP_NAME_CAP + }' -e" + ) else: exec_shell_command_async( f'swww img "{ full_path }" -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest' ) + exec_shell_command_async( + f"notify-send '🖼️ Wallpaper Applied' 'File: {filename}\\nMatugen: Disabled' -a '{ + data.APP_NAME_CAP + }' -e" + ) def _set_random_wallpaper(self): """Set a random wallpaper.""" @@ -195,7 +226,7 @@ class WallpaperPlugin(PluginBase): ) def _apply_hex_color(self, hex_color: str, scheme: str = None): - """Apply hex color using matugen.""" + """Apply hex color using matugen. Assumes matugen is enabled.""" if not hex_color.startswith("#"): hex_color = "#" + hex_color @@ -243,13 +274,12 @@ class WallpaperPlugin(PluginBase): ) # Special commands - if query == "random" or query.startswith("random"): - # Execute immediately when this result is created/selected - self._set_random_wallpaper() + if query.strip() == "random": + # Show result for random wallpaper (execute on Enter) results.append( Result( - title=f"Random Wallpaper Applied{indicator_text}", - subtitle=f"A random wallpaper has been set • {status_text}", + title=f"Random Wallpaper{indicator_text}", + subtitle=f"Set a random wallpaper • {status_text}", icon_markup=random.choice( [ icons.dice_1, @@ -260,13 +290,39 @@ class WallpaperPlugin(PluginBase): icons.dice_6, ] ), - action=lambda: None, # No action needed since already executed + action=lambda: self._set_random_wallpaper(), relevance=1.0, plugin_name=self.display_name, data={ "action": "random", - "executed": True, "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_markup=random.choice( + [ + icons.dice_1, + icons.dice_2, + icons.dice_3, + icons.dice_4, + icons.dice_5, + icons.dice_6, + ] + ), + 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, }, ) ) @@ -287,48 +343,151 @@ class WallpaperPlugin(PluginBase): hex_match = re.search(r"#?([0-9A-Fa-f]{6})", query) if hex_match: hex_color = "#" + hex_match.group(1) - # Execute immediately - self._apply_hex_color(hex_color, scheme) scheme_name = self.schemes.get(scheme, scheme) - results.append( - Result( - title=f"Hex Color Applied: {hex_color}{indicator_text}", - subtitle=f"Applied with {scheme_name} scheme • {status_text}", - icon_markup=icons.palette, - action=lambda: None, # No action needed since already executed - relevance=1.0, - plugin_name=self.display_name, - data={ - "action": "hex_color", - "color": hex_color, - "scheme": scheme, - "executed": True, - "bypass_max_results": True, - }, + + # 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: + # Check if matugen is enabled + if 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_markup=icons.palette, + action=lambda c=hex_color, s=scheme: self._apply_hex_color(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 disabled + results.append( + Result( + title=f"Cannot Apply Hex Color: {hex_color}{indicator_text}", + subtitle=f"Matugen is disabled • Enable matugen to use hex colors", + icon_markup=icons.palette, + action=lambda: None, + relevance=1.0, + plugin_name=self.display_name, + data={ + "action": "hex_color_failed", + "color": hex_color, + "scheme": scheme, + "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_markup=icons.palette, + action=lambda c=hex_color, s=scheme: self._apply_hex_color(c, s) if 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=f"Complete the 6-digit hex color to apply", + icon_markup=icons.palette, + 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: - # Random hex color - hex_color = self._generate_random_hex_color() - self._apply_hex_color(hex_color, scheme) - scheme_name = self.schemes.get(scheme, scheme) - results.append( - Result( - title=f"Random Color Applied: {hex_color}{indicator_text}", - subtitle=f"Applied with {scheme_name} scheme • {status_text}", - icon_markup=icons.palette, - action=lambda: None, # No action needed since already executed - relevance=1.0, - plugin_name=self.display_name, - data={ - "action": "random_hex", - "color": hex_color, - "scheme": scheme, - "executed": True, - "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 enabled + if 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_markup=icons.palette, + action=lambda s=scheme: self._apply_hex_color(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 disabled + results.append( + Result( + title=f"Cannot Apply Random Color{indicator_text}", + subtitle=f"Matugen is disabled • Enable matugen to use hex colors", + icon_markup=icons.palette, + 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_markup=icons.palette, + action=lambda: self._apply_hex_color(self._generate_random_hex_color(), scheme) if matugen_enabled else None, + relevance=0.8, + plugin_name=self.display_name, + data={ + "action": "random_hex_suggestion", + "bypass_max_results": True, + "keep_launcher_open": True, + }, + ) ) - ) else: # Show hex color help results.append( @@ -384,6 +543,7 @@ class WallpaperPlugin(PluginBase): "action": "scheme_select", "scheme": scheme_id, "bypass_max_results": True, + "keep_launcher_open": True, }, ) ) @@ -391,78 +551,108 @@ class WallpaperPlugin(PluginBase): # Matugen controls if "matugen" in query: current_state = self._get_matugen_state() - if "on" in query or "enable" in query: - # Execute immediately - self._set_matugen_state(True) - # Get updated status after change - new_indicator_text, new_status_text, _, _ = ( - self._get_status_indicators() - ) + + # 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"Matugen Enabled{new_indicator_text}", - subtitle=f"Dynamic color generation is now enabled • { - new_status_text - }", + title=f"Enable Matugen{indicator_text}", + subtitle=f"Turn on dynamic color generation • {status_text}", icon_markup=icons.palette, - action=lambda: None, # No action needed since already executed + action=lambda: self._set_matugen_state(True), relevance=0.9, plugin_name=self.display_name, data={ "action": "matugen_on", - "executed": True, "bypass_max_results": True, + "keep_launcher_open": True, }, ) ) - elif "off" in query or "disable" in query: - # Execute immediately - self._set_matugen_state(False) - # Get updated status after change - new_indicator_text, new_status_text, _, _ = ( - self._get_status_indicators() - ) + elif query.strip() == "matugen off" or query.strip() == "matugen disable": + # Show result for disabling matugen (execute on Enter) results.append( Result( - title=f"Matugen Disabled{new_indicator_text}", - subtitle=f"Dynamic color generation is now disabled • { - new_status_text - }", + title=f"Disable Matugen{indicator_text}", + subtitle=f"Turn off dynamic color generation • {status_text}", icon_markup=icons.palette, - action=lambda: None, # No action needed since already executed + action=lambda: self._set_matugen_state(False), relevance=0.9, plugin_name=self.display_name, data={ "action": "matugen_off", - "executed": True, "bypass_max_results": True, + "keep_launcher_open": True, }, ) ) - elif "toggle" in query: - # Execute toggle immediately + elif query.strip() == "matugen toggle": + # Show result for toggling matugen (execute on Enter) new_state = not current_state - self._set_matugen_state(new_state) - # Get updated status after change - new_indicator_text, new_status_text, _, _ = ( - self._get_status_indicators() - ) results.append( Result( - title=f"Matugen {'Enabled' if new_state else 'Disabled'}{ - new_indicator_text - }", - subtitle=f"Dynamic color generation is now { - 'enabled' if new_state else 'disabled' - } • {new_status_text}", + 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_markup=icons.palette, - action=lambda: None, # No action needed since already executed + action=lambda: self._set_matugen_state(new_state), relevance=0.9, plugin_name=self.display_name, data={ "action": "matugen_toggle", - "executed": True, "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_markup=icons.palette, + 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_markup=icons.palette, + 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_markup=icons.palette, + 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, }, ) ) @@ -531,7 +721,11 @@ class WallpaperPlugin(PluginBase): action=lambda: self._set_random_wallpaper(), relevance=0.9, plugin_name=self.display_name, - data={"action": "random_quick", "bypass_max_results": True}, + data={ + "action": "random_quick", + "bypass_max_results": True, + "keep_launcher_open": True, + }, ) ) @@ -587,6 +781,7 @@ class WallpaperPlugin(PluginBase): "wallpaper": wallpaper, "action": "set", "bypass_max_results": True, + "keep_launcher_open": True, }, ) ) diff --git a/modules/launcher/trigger_config.py b/modules/launcher/trigger_config.py index 1de7451..3591997 100644 --- a/modules/launcher/trigger_config.py +++ b/modules/launcher/trigger_config.py @@ -10,31 +10,6 @@ import utils.icons as icons from fabric.utils import get_relative_path -def load_launcher_config(config_path: str) -> Dict: - """Load trigger configuration from JSON file""" - config = {"launcher_config": {}, "settings": {}} - - if os.path.exists(config_path): - try: - with open(config_path, "r", encoding="utf-8") as f: - config = json.load(f) - except Exception as e: - print(f"Error loading trigger config: {e}") - - return config - - -def save_launcher_config(config_path: str, config: Dict) -> bool: - """Save trigger configuration to JSON file""" - try: - os.makedirs(os.path.dirname(config_path), exist_ok=True) - with open(config_path, "w", encoding="utf-8") as f: - json.dump(config, f, indent=2, ensure_ascii=False) - return True - except Exception as e: - print(f"Error saving trigger config: {e}") - return False - class TriggerConfig: def __init__(self, config_path: str = None): @@ -42,17 +17,29 @@ class TriggerConfig: config_path = get_relative_path("../../config/launcher.json") self.config_path = config_path - self.config = load_launcher_config(config_path) + + # Load configuration from JSON file + config = {"launcher_config": {}, "settings": {}} + if os.path.exists(config_path): + try: + with open(config_path, "r", encoding="utf-8") as f: + config = json.load(f) + except Exception as e: + print(f"Error loading trigger config: {e}") + + self.config = config self.launcher_config = self.config.get("launcher_config", {}) - self.settings = self.config.get("settings", {}) - if not self.launcher_config: - self._create_default_config() + # Initialize settings with defaults + default_settings = { + "max_examples_shown": 2, + "default_icon": "apps", + "fallback_example_template": "{trigger} ", + "config_version": "1.0", + } + self.settings = {**default_settings, **self.config.get("settings", {})} + - def save_config(self) -> bool: - self.config["launcher_config"] = self.launcher_config - self.config["settings"] = self.settings - return save_launcher_config(self.config_path, self.config) def get_trigger_examples(self, trigger: str) -> List[str]: examples = self.launcher_config.get(trigger, {}).get("examples", []) @@ -77,59 +64,8 @@ class TriggerConfig: def get_all_triggers(self) -> Dict[str, Dict[str, Any]]: return self.launcher_config.copy() - def add_trigger( - self, - trigger: str, - examples: List[str], - icon: str = None, - description: str = None, - ) -> bool: - try: - self.launcher_config[trigger] = { - "examples": examples, - "icon": icon or self.settings.get("default_icon", "system-search"), - "description": description or f"{trigger} - Custom trigger", - } - return True - except Exception as e: - print(f"Error adding trigger {trigger}: {e}") - return False - def remove_trigger(self, trigger: str) -> bool: - try: - if trigger in self.launcher_config: - del self.launcher_config[trigger] - return True - return False - except Exception as e: - print(f"Error removing trigger {trigger}: {e}") - return False - def get_max_examples_shown(self) -> int: - return self.settings.get("max_examples_shown", 2) - def _create_default_config(self): - self.launcher_config = { - "app": { - "examples": ["app firefox", "app chrome", "app terminal"], - "icon": "apps", - "description": "Applications - Launch installed applications", - }, - "calc": { - "examples": ["calc 2+2", "calc sqrt(16)", "calc pi*2"], - "icon": "calculator", - "description": "Calculator - Perform mathematical calculations", - }, - "file": { - "examples": ["file document.pdf", "file *.py", "file config"], - "icon": "file", - "description": "Files - Search for files and documents", - }, - } - self.settings = { - "max_examples_shown": 2, - "default_icon": "apps", - "fallback_example_template": "{trigger} ", - "config_version": "1.0", - } + diff --git a/styles/colors.css b/styles/colors.css index 8247b8d..04b46de 100644 --- a/styles/colors.css +++ b/styles/colors.css @@ -1,32 +1,32 @@ :vars { - --foreground: #f0dfd6; - --background: #19120d; - --cursor: #f0dfd6; - --primary: #ffb784; - --on-primary: #502500; - --secondary: #e4bfa8; - --on-secondary: #422b1b; - --tertiary: #c8ca94; - --on-tertiary: #30330b; - --surface: #19120d; - --surface-bright: #413731; + --foreground: #e3e2e9; + --background: #121318; + --cursor: #e3e2e9; + --primary: #b4c5ff; + --on-primary: #1b2d60; + --secondary: #c1c5dd; + --on-secondary: #2b3042; + --tertiary: #e2bbdb; + --on-tertiary: #422740; + --surface: #121318; + --surface-bright: #38393f; --error: #ffb4ab; --error-dim: #ff8678; --on-error: #690005; --error-container: #93000a; - --outline: #9f8d83; + --outline: #8f909a; --shadow: #000000; - --red: #ffb598; - --red-dim: #ff9065; - --green: #b7d085; - --green-dim: #a1c260; - --yellow: #dec56e; - --yellow-dim: #d5b444; - --blue: #cebdfe; - --blue-dim: #a98bfd; - --magenta: #fcb0d5; - --magenta-dim: #fa7fbb; - --cyan: #84d5c4; - --cyan-dim: #5ec8b2; + --red: #ffb2b9; + --red-dim: #ff7f8b; + --green: #95d5a7; + --green-dim: #70c789; + --yellow: #b8cf84; + --yellow-dim: #a3c15f; + --blue: #b9c3ff; + --blue-dim: #8697ff; + --magenta: #e4b7f3; + --magenta-dim: #d48bec; + --cyan: #82d3e2; + --cyan-dim: #59c4d8; --white: #82d3e0; }