diff --git a/modules/launcher/main.py b/modules/launcher/main.py index 97bc8de..08cc695 100644 --- a/modules/launcher/main.py +++ b/modules/launcher/main.py @@ -1,15 +1,15 @@ from typing import List, Optional, Tuple +from gi.repository import Gdk, GLib + from fabric.core.service import Property from fabric.widgets.box import Box from fabric.widgets.entry import Entry -from fabric.widgets.scrolledwindow import ScrolledWindow -from gi.repository import Gdk, GLib - from modules.launcher.plugin_manager import PluginManager from modules.launcher.result import Result from modules.launcher.result_item import ResultItem from modules.launcher.trigger_config import TriggerConfig +from widgets.smartscrolled import SmartScrolledBox from widgets.wayland import WaylandWindow as Window # Constants @@ -44,26 +44,15 @@ class Launcher(Window): keyboard_mode="exclusive", **kwargs, ) - - # Initialization flag to prevent callbacks during setup self._initializing = True - - # Flag to prevent recursion when automatically adding spaces self._auto_adding_space = False - - # Flag to prevent search change handler interference during backspace processing self._processing_backspace = False - - # Initialize plugin manager self.plugin_manager = PluginManager() - - # Initialize trigger configuration self.trigger_config = TriggerConfig() - # Current results and selection self.results: List[Result] = [] self.selected_index = 0 - self.max_results = 8 # Spotlight typically shows fewer results + self.max_results = 5 # Show 4 applications by default instead of list # Trigger system self.triggered_plugin = None # Currently active triggered plugin @@ -75,6 +64,16 @@ class Launcher(Window): # Focus management self.focus_mode = "search" # "search", "results" + # Mouse activity tracking + self._mouse_active = False + self._last_mouse_activity = 0 + self._mouse_activity_timeout = 1000 # 1 second timeout + self._keyboard_used_recently = False + self._last_keyboard_activity = 0 + self._keyboard_activity_timeout = 2000 # 2 second timeout + self._launcher_just_opened = False + self._mouse_interaction_delay = 500 # 500ms delay after opening + # Setup UI main_box = Box( name="launcher", @@ -107,28 +106,29 @@ class Launcher(Window): main_box.add(self.header_box) - # Results container with increased height for Spotlight-style - self.results_scroll = ScrolledWindow( - name="launcher-results-scroll", - h_scrollbar_policy="never", - min_content_size=(LAUNCHER_WIDTH, LAUNCHER_HEIGHT), - max_content_size=(LAUNCHER_WIDTH, LAUNCHER_HEIGHT), - propagate_width=False, - propagate_height=False, - ) + self.results_scroll = SmartScrolledBox(max_height=LAUNCHER_HEIGHT) self.results_box = Box( name="launcher-results", orientation="v", spacing=0, ) - self.results_scroll.add(self.results_box) + self.results_scroll.append(self.results_box) main_box.add(self.results_scroll) - # Initially hide the results container - self.results_scroll.hide() + # Keep the results container visible initially self.connect("key-press-event", self._on_key_press) + + # Connect mouse events to track activity + self.results_scroll.connect("button-press-event", self._on_mouse_activity) + self.results_scroll.connect("motion-notify-event", self._on_mouse_activity) + self.results_scroll.connect("scroll-event", self._on_mouse_activity) + + # Also track mouse activity on the main window for better coverage + self.connect("button-press-event", self._on_mouse_activity) + self.connect("motion-notify-event", self._on_mouse_activity) + self.hide() # Mark initialization as complete @@ -144,6 +144,20 @@ class Launcher(Window): trigger_keyword: Optional trigger keyword to activate immediately (e.g., "google", "calc", "app") external: If True, execute the command without showing the launcher UI """ + # Reset mouse activity tracking when launcher is shown + self._mouse_active = False + self._last_mouse_activity = 0 + self._keyboard_used_recently = False + self._last_keyboard_activity = 0 + self._launcher_just_opened = True + + # Schedule to allow mouse interactions after a delay + def enable_mouse_interactions(): + self._launcher_just_opened = False + return False + + GLib.timeout_add(self._mouse_interaction_delay, enable_mouse_interactions) + if external and trigger_keyword: # Execute command externally without showing launcher return self._execute_external_command(trigger_keyword) @@ -180,10 +194,11 @@ class Launcher(Window): self.search_entry.set_text("") self._clear_results() else: - # Normal launcher opening - clear everything + # Normal launcher opening - show applications self.opened_with_trigger = False self.search_entry.set_text("") - self._clear_results() + # Trigger initial search to show applications immediately + self._perform_search("") # Reset focus mode to search self.focus_mode = "search" @@ -305,8 +320,8 @@ class Launcher(Window): # Debounce search to avoid too many queries GLib.timeout_add(SEARCH_DEBOUNCE_MS, self._perform_search, query) else: - # Hide results when query is empty - self._clear_results() + # Show applications when query is empty + self._perform_search("") def _perform_search(self, query: str) -> bool: """Perform search across all plugins.""" @@ -315,10 +330,25 @@ class Launcher(Window): return False if not query: - # Empty query - reset trigger mode and hide results + # Empty query - show popular applications self.triggered_plugin = None self.active_trigger = "" - self._clear_results() + + # Get applications plugin and show popular apps + applications_plugin = self._get_applications_plugin() + if applications_plugin: + try: + # Query with empty string to get popular/frequently used applications + all_results = applications_plugin.query("") + # Limit to max_results for empty query + self.results = all_results[: self.max_results] + self.selected_index = 0 + self._update_results_display() + except Exception as e: + print(f"Error getting applications: {e}") + self._clear_results() + else: + self._clear_results() return False # Check if we're already in trigger mode @@ -365,13 +395,24 @@ class Launcher(Window): print(f"Error in triggered plugin {triggered_plugin.name}: {e}") all_results = [] else: - # No trigger detected - only show trigger suggestions + # No trigger detected - search applications and show trigger suggestions self.triggered_plugin = None self.active_trigger = "" - # Show trigger suggestions if query matches trigger prefixes + all_results = [] + + # Search applications directly without trigger + applications_plugin = self._get_applications_plugin() + if applications_plugin: + try: + app_results = applications_plugin.query(query) + all_results.extend(app_results) + except Exception as e: + print(f"Error searching applications: {e}") + + # Also show trigger suggestions if query matches trigger prefixes trigger_suggestions = self._get_trigger_suggestions(query) - all_results = trigger_suggestions + all_results.extend(trigger_suggestions) # Sort results by relevance score all_results.sort(key=lambda r: r.relevance, reverse=True) @@ -387,8 +428,19 @@ class Launcher(Window): # In trigger mode - show all results from the triggered plugin self.results = all_results elif not has_bypass: - # Global search or trigger suggestions - apply max_results limit - self.results = all_results[: self.max_results] + # Global search - check if it's an application search + applications_plugin = self._get_applications_plugin() + is_application_search = applications_plugin and any( + hasattr(r, "plugin_name") and r.plugin_name == "Applications" + for r in all_results + ) + + if is_application_search: + # Don't limit application search results + self.results = all_results + else: + # Apply max_results limit for other global searches and trigger suggestions + self.results = all_results[: self.max_results] else: # Has bypass flag - show all results self.results = all_results @@ -462,6 +514,16 @@ class Launcher(Window): return None, "" + def _get_applications_plugin(self): + """Get the applications plugin instance.""" + for plugin in self.plugin_manager.get_active_plugins(): + if ( + hasattr(plugin, "display_name") + and plugin.display_name == "Applications" + ): + return plugin + return None + def _get_trigger_suggestions(self, query: str) -> List[Result]: """ Get trigger suggestions based on the current query. @@ -515,7 +577,7 @@ class Launcher(Window): return Result( title=f"{trigger_clean}", subtitle=f"{description} - {', '.join(examples[:max_examples])}", - icon_markup=icon_name, + icon_name=icon_name, action=lambda t=trigger_clean: self._activate_trigger(t), # Shorter triggers get higher relevance relevance=100 - len(trigger_clean), @@ -678,11 +740,11 @@ class Launcher(Window): self.results_box.show_all() - # Show/hide scroll container based on results - if self.results: - self.results_scroll.show() - else: - self.results_scroll.hide() + # Force height constraints after adding results + self.results_scroll.force_height_constraint() + + # Always show scroll container + self.results_scroll.show() def _update_input_action_text(self): """Update the input field with action text (Spotlight-style).""" @@ -716,8 +778,6 @@ class Launcher(Window): # Not in trigger mode - show Spotlight-style help if current_text == ":": self.search_entry.set_placeholder_text("Available search triggers") - elif current_text: - self.search_entry.set_placeholder_text("Spotlight Search") else: self.search_entry.set_placeholder_text("Spotlight Search") @@ -727,7 +787,7 @@ class Launcher(Window): self.selected_index = 0 for child in self.results_box.get_children(): self.results_box.remove(child) - self.results_scroll.hide() + # Keep the results scroll visible even when empty def _handle_escape_key(self) -> bool: """Handle escape key press.""" @@ -816,6 +876,13 @@ class Launcher(Window): def _on_key_press(self, _widget, event): """Handle key press events.""" + # Track keyboard activity + import time + + current_time = int(time.time() * 1000) + self._last_keyboard_activity = current_time + self._keyboard_used_recently = True + keyval = event.keyval # Escape - handle password entry, exit trigger mode, or hide launcher @@ -1011,16 +1078,58 @@ class Launcher(Window): def _on_result_clicked(self, _result_item, index): """Handle result item click.""" - self.selected_index = index - self._activate_selected() + # Only allow clicks when mouse is active or keyboard hasn't been used recently + if self._should_allow_mouse_interaction(): + self.selected_index = index + self._activate_selected() def _on_result_hovered(self, index): """Handle result item hover.""" - # Update selection when mouse hovers over a result (without scrolling) - if 0 <= index < len(self.results): - self.selected_index = index - self.focus_mode = "results" # Switch to results focus mode - self._update_selection_visual_only() + # Only allow hover selection when mouse is active and keyboard hasn't been used recently + if self._should_allow_mouse_interaction(): + if 0 <= index < len(self.results): + self.selected_index = index + self.focus_mode = "results" # Switch to results focus mode + self._update_selection_visual_only() + + def _on_mouse_activity(self, widget, event): + """Track mouse activity to determine when mouse interactions should be enabled.""" + import time + + current_time = int(time.time() * 1000) + self._last_mouse_activity = current_time + self._mouse_active = True + + # Schedule a check to disable mouse activity after timeout + def check_mouse_timeout(): + if current_time - self._last_mouse_activity >= self._mouse_activity_timeout: + self._mouse_active = False + return False + + GLib.timeout_add(self._mouse_activity_timeout, check_mouse_timeout) + return False + + def _should_allow_mouse_interaction(self): + """Determine if mouse interactions should be allowed.""" + import time + + current_time = int(time.time() * 1000) + + # Don't allow mouse interactions immediately after launcher opens + if self._launcher_just_opened: + return False + + # Allow mouse interaction if: + # 1. Mouse is currently active (recent activity) + # 2. OR keyboard hasn't been used recently (2 seconds) + mouse_recently_active = ( + current_time - self._last_mouse_activity + ) < self._mouse_activity_timeout + keyboard_not_recently_used = ( + current_time - self._last_keyboard_activity + ) > self._keyboard_activity_timeout + + return mouse_recently_active or keyboard_not_recently_used def _is_mouse_over_results(self): """Check if mouse is over the results area.""" diff --git a/modules/launcher/plugins/applications.py b/modules/launcher/plugins/applications.py index 1e904f9..50503fe 100644 --- a/modules/launcher/plugins/applications.py +++ b/modules/launcher/plugins/applications.py @@ -4,7 +4,6 @@ from typing import List from fabric.utils import DesktopApp from fabric.utils.helpers import get_desktop_applications, get_relative_path - from modules.dock import Dock from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -17,8 +16,7 @@ class ApplicationsPlugin(PluginBase): self.description = "Search and launch desktop applications" def initialize(self): - # Set up triggers for applications - both with and without spaces - self.set_triggers(["app"]) + pass def cleanup(self): pass @@ -71,9 +69,8 @@ class ApplicationsPlugin(PluginBase): def query(self, query_string: str) -> List[Result]: """Search applications based on query.""" if not query_string.strip(): - return [] + return self._get_all_applications() - # Get fresh applications list each time (like examples/app-launcher) try: applications = get_desktop_applications(include_hidden=False) except Exception as e: @@ -86,9 +83,8 @@ class ApplicationsPlugin(PluginBase): for app in applications: relevance = self._calculate_relevance(app, query) if relevance > 0: - # Truncate description to prevent overflow beyond 550px window description = app.description or app.generic_name or "" - if len(description) > 80: # Limit to ~80 characters to fit in 550px + if len(description) > 80: description = description[:70] + "..." result = Result( @@ -161,3 +157,35 @@ class ApplicationsPlugin(PluginBase): def _launch_application(self, app: DesktopApp): app.launch() + + def _get_all_applications(self) -> List[Result]: + """Get a list of all available applications.""" + try: + applications = get_desktop_applications(include_hidden=False) + except Exception as e: + print(f"Failed to load applications: {e}") + return [] + + results = [] + + for app in applications: + # Truncate description + description = app.description or app.generic_name or "" + if len(description) > 80: + description = description[:70] + "..." + + result = Result( + title=app.display_name or app.name, + subtitle=description, + icon=app.get_icon_pixbuf(size=48), + action=lambda a=app: self._launch_application(a), + relevance=0.5, # Default relevance for all apps + plugin_name=self.display_name, + data={ + "app": app, + "pin_action": lambda a=app: self._pin_application(a), + }, + ) + results.append(result) + + return results diff --git a/modules/launcher/plugins/bash_scripts.py b/modules/launcher/plugins/bash_scripts.py index dff923b..04aae0e 100644 --- a/modules/launcher/plugins/bash_scripts.py +++ b/modules/launcher/plugins/bash_scripts.py @@ -4,10 +4,8 @@ import threading import time from typing import Dict, List -from fabric.utils import exec_shell_command_async - import config.data as data -import utils.icons as icons +from fabric.utils import exec_shell_command_async from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -38,17 +36,17 @@ class BashScriptsPlugin(PluginBase): "pm balanced": { "description": "Switch to balanced power profile", "profile": "balanced", - "icon": icons.power_balanced, + "icon": "power-balanced", }, "pm performance": { "description": "Switch to performance power profile", "profile": "performance", - "icon": icons.power_performance, + "icon": "power-performance", }, "pm saver": { "description": "Switch to power saver profile", "profile": "power-saver", - "icon": icons.power_saving, + "icon": "power-saving", }, } @@ -134,12 +132,16 @@ class BashScriptsPlugin(PluginBase): if actual_profile: self._battery_service._profile_proxy.ActiveProfile = actual_profile print( - f"BashScriptsPlugin: Successfully set power profile to {actual_profile}" + f"BashScriptsPlugin: Successfully set power profile to { + actual_profile + }" ) return True else: print( - f"BashScriptsPlugin: No matching profile found for '{profile}' in available profiles: {available_profiles}" + f"BashScriptsPlugin: No matching profile found for '{ + profile + }' in available profiles: {available_profiles}" ) return False @@ -186,7 +188,6 @@ class BashScriptsPlugin(PluginBase): current_time - self._last_cache_update > self._cache_update_interval or not self._scripts_cache ): - if not self._cache_building: self._cache_building = True self._cache_thread = threading.Thread( @@ -197,7 +198,6 @@ class BashScriptsPlugin(PluginBase): def _build_scripts_cache_background(self): """Build scripts cache in background thread.""" try: - new_cache = {} # Scan Modus scripts directory for discovered scripts @@ -208,7 +208,9 @@ class BashScriptsPlugin(PluginBase): self._scan_directory_for_scripts(self.modus_scripts_dir, new_cache) except (PermissionError, FileNotFoundError, OSError) as e: print( - f"BashScriptsPlugin: Error scanning Modus scripts directory: {e}" + f"BashScriptsPlugin: Error scanning Modus scripts directory: { + e + }" ) # Update cache atomically @@ -423,13 +425,12 @@ class BashScriptsPlugin(PluginBase): or query_lower in cmd_lower or query_lower in description_lower ): - relevance = 1.0 if query_lower == cmd_lower else 0.9 result = Result( title=cmd, subtitle=info["description"], - icon_markup=info["icon"], + icon_name=info["icon"], action=self._create_power_action(info["profile"]), relevance=relevance, plugin_name=self.display_name, @@ -451,7 +452,7 @@ class BashScriptsPlugin(PluginBase): result = Result( title=cmd, subtitle=info["description"], - icon_markup=info["icon"], + icon_name=info["icon"], action=self._create_power_action(info["profile"]), relevance=0.8, plugin_name=self.display_name, @@ -473,9 +474,10 @@ class BashScriptsPlugin(PluginBase): if success: # Clear the search query after successful execution try: - from fabric import Application from gi.repository import GLib + from fabric import Application + app = Application.get_default() if app and hasattr(app, "launcher"): launcher = app.launcher @@ -517,16 +519,16 @@ class BashScriptsPlugin(PluginBase): # Choose icon based on script type and status if not executable: - icon_markup = icons.file + icon_name = "gtk-file" elif script_type == "custom": - icon_markup = icons.star + icon_name = "folder-script-symbolic" else: - icon_markup = icons.terminal + icon_name = "terminalc" return Result( title=script_name, subtitle=subtitle, - icon_markup=icon_markup, + icon_name=icon_name, action=self._create_script_action(script_name, script_info), relevance=relevance, plugin_name=self.display_name, @@ -556,7 +558,7 @@ class BashScriptsPlugin(PluginBase): variant_result = Result( title=f"{script_name} {arg}", subtitle=f"{desc} | [scripts]", - icon_markup=icons.terminal, + icon_name="terminal-symbolic", action=self._create_script_action_with_args( script_name, script_info, [arg] ), diff --git a/modules/launcher/plugins/bookmarks.py b/modules/launcher/plugins/bookmarks.py index 4885938..ee55139 100644 --- a/modules/launcher/plugins/bookmarks.py +++ b/modules/launcher/plugins/bookmarks.py @@ -6,10 +6,9 @@ from pathlib import Path from typing import Dict, List, Optional from urllib.parse import urlparse -from fabric.utils.helpers import get_relative_path from thefuzz import fuzz -import utils.icons as icons +from fabric.utils.helpers import get_relative_path from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -268,7 +267,7 @@ class BookmarksPlugin(PluginBase): Result( title="No Bookmarks Found", subtitle="Use 'add <url>' to add first bookmark", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -298,7 +297,7 @@ class BookmarksPlugin(PluginBase): Result( title="No Bookmarks Yet", subtitle="Use 'add <title> <url>' to add first bookmark", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -307,7 +306,7 @@ class BookmarksPlugin(PluginBase): Result( title="Example: Add Google", subtitle="add Google https://google.com", - icon_markup=icons.bulb, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -334,7 +333,7 @@ class BookmarksPlugin(PluginBase): Result( title="Add Bookmark", subtitle="Usage: add <title> <url> [description]", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -343,7 +342,7 @@ class BookmarksPlugin(PluginBase): Result( title="Example", subtitle="add Google https://google.com Search engine", - icon_markup=icons.bulb, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -358,7 +357,7 @@ class BookmarksPlugin(PluginBase): Result( title="Invalid Format", subtitle="Usage: add <title> <url> [description]", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -387,7 +386,7 @@ class BookmarksPlugin(PluginBase): Result( title="Bookmark Already Exists", subtitle=f"URL '{display_url}' already exists", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -407,7 +406,7 @@ class BookmarksPlugin(PluginBase): # Truncate description to prevent launcher resize max_desc_len = 35 - len(domain) # Account for domain + separator if len(description) > max_desc_len: - description = description[:max_desc_len-3] + "..." + description = description[: max_desc_len - 3] + "..." subtitle += f" • {description}" # Truncate title for display @@ -419,7 +418,7 @@ class BookmarksPlugin(PluginBase): Result( title=f"Add bookmark '{display_title}'", subtitle=subtitle, - icon_markup=icons.plus, + icon_name="plus", action=lambda: self._add_bookmark_action(title, url, description), relevance=1.0, plugin_name=self.display_name, @@ -451,7 +450,7 @@ class BookmarksPlugin(PluginBase): Result( title="Bookmark Not Found", subtitle=f"No bookmark found matching '{identifier}'", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -477,7 +476,7 @@ class BookmarksPlugin(PluginBase): Result( title=f"Remove '{display_title}'?", subtitle=f"Click to confirm: {display_domain}", - icon_markup=icons.trash, + icon_name="trash", action=lambda: self._remove_bookmark_action(identifier), relevance=1.0, plugin_name=self.display_name, @@ -492,7 +491,7 @@ class BookmarksPlugin(PluginBase): Result( title="Remove Bookmark", subtitle="Usage: remove <title|url|domain>", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -506,7 +505,7 @@ class BookmarksPlugin(PluginBase): Result( title="Available Bookmarks:", subtitle=f"{len(bookmarks)} bookmarks available to remove", - icon_markup=icons.bookmark, + icon_name="bookmarks-organize", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -532,7 +531,7 @@ class BookmarksPlugin(PluginBase): Result( title=f"remove {display_title}", subtitle=f"Click to remove: {display_title} ({display_domain})", - icon_markup=icons.trash, + icon_name="trash", action=lambda t=title: self._remove_bookmark_action(t), relevance=0.8, plugin_name=self.display_name, @@ -643,9 +642,10 @@ class BookmarksPlugin(PluginBase): if description: # Truncate description to prevent launcher resize - max_desc_len = 50 - len(domain) # Account for domain + separator + # Account for domain + separator + max_desc_len = 50 - len(domain) if len(description) > max_desc_len: - description = description[:max_desc_len-3] + "..." + description = description[: max_desc_len - 3] + "..." subtitle = f"{domain} • {description}" else: subtitle = domain @@ -657,7 +657,7 @@ class BookmarksPlugin(PluginBase): return Result( title=title, subtitle=subtitle, - icon_markup=icons.bookmark, + icon_name="bookmark-organize", action=lambda u=url: self._open_bookmark(u), relevance=relevance, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/caffeine.py b/modules/launcher/plugins/caffeine.py index 433d0c1..decde97 100644 --- a/modules/launcher/plugins/caffeine.py +++ b/modules/launcher/plugins/caffeine.py @@ -3,11 +3,10 @@ from threading import Timer from typing import List import gi -from fabric.utils import get_relative_path -from fabric.utils.helpers import exec_shell_command_async import config.data as data -import utils.icons as icons +from fabric.utils import get_relative_path +from fabric.utils.helpers import exec_shell_command_async from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -173,7 +172,7 @@ class CaffeinePlugin(PluginBase): result = Result( title=f"Custom: {query}", subtitle="Set custom duration", - icon_markup=icons.coffee, + icon_name="caffeine", action=self._create_inhibit_action(query), relevance=1.0, plugin_name=self.display_name, @@ -194,7 +193,7 @@ class CaffeinePlugin(PluginBase): result = Result( title=description, subtitle=subtitle, - icon_markup=icons.coffee, + icon_name="caffeine", action=self._create_inhibit_action(duration), relevance=0.9 if query == duration else 0.7, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/calculator.py b/modules/launcher/plugins/calculator.py index e90e468..f97dddb 100644 --- a/modules/launcher/plugins/calculator.py +++ b/modules/launcher/plugins/calculator.py @@ -4,7 +4,6 @@ import subprocess import time from typing import List -import utils.icons as icons from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result from utils.conversion import Conversion @@ -99,7 +98,7 @@ class CalculatorPlugin(PluginBase): Result( title=f"{result:.6g} {to_unit}", subtitle=subtitle, - icon_markup=icons.calculator, + icon_name="calculator-symbolic", action=lambda r=f"{result:.6g}": self._copy_to_clipboard(r), relevance=1.0, plugin_name=self.display_name, @@ -111,7 +110,7 @@ class CalculatorPlugin(PluginBase): Result( title="Invalid conversion", subtitle=str(e), - icon_markup=icons.calculator, + icon_name="calculator-symbolic", relevance=0.0, plugin_name=self.display_name, ) @@ -127,7 +126,7 @@ class CalculatorPlugin(PluginBase): Result( title=f"{result:.6g}", subtitle=f"{query} = {result:.6g}", - icon_markup=icons.calculator, + icon_name="calculator-symbolic", action=lambda r=f"{result:.6g}": self._copy_to_clipboard(r), relevance=1.0, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/emoji.py b/modules/launcher/plugins/emoji.py index 834d673..8842e80 100644 --- a/modules/launcher/plugins/emoji.py +++ b/modules/launcher/plugins/emoji.py @@ -5,9 +5,8 @@ import time from collections import OrderedDict from typing import Dict, List -from fabric.utils import get_relative_path - import config.data as data +from fabric.utils import get_relative_path from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result diff --git a/modules/launcher/plugins/otp.py b/modules/launcher/plugins/otp.py index 86362f9..7ab0b6d 100644 --- a/modules/launcher/plugins/otp.py +++ b/modules/launcher/plugins/otp.py @@ -6,8 +6,6 @@ from pathlib import Path from typing import Dict, List, Optional from fabric.utils import get_relative_path - -import utils.icons as icons from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result from services.auth import ( @@ -354,7 +352,7 @@ class OTPPlugin(PluginBase): Result( title="Invalid format", subtitle="Usage: add <account_name> <secret> or add <account_name>```<secret>```", - icon_markup=icons.question, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -373,7 +371,7 @@ class OTPPlugin(PluginBase): Result( title="Error adding account", subtitle=f"Debug: {str(e)}", - icon_markup=icons.cancel, + icon_name="cancel", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -390,7 +388,7 @@ class OTPPlugin(PluginBase): Result( title="No OTP accounts configured", subtitle="Use 'add <account> <secret>' to add your first account", - icon_markup=icons.key, + icon_name="gtk-authentication-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -401,7 +399,7 @@ class OTPPlugin(PluginBase): Result( title="Available commands:", subtitle="add <account> <secret> | remove <account> | qr <account>", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -425,7 +423,7 @@ class OTPPlugin(PluginBase): subtitle_markup=f"{display_name} • { time_display } remaining • Shift+Enter: remove", - icon_markup=icons.key, + icon_name="gtk-authentication-symbolic", action=lambda code=totp_code: self._copy_to_clipboard(code), relevance=1.0, plugin_name=self.display_name, @@ -444,7 +442,7 @@ class OTPPlugin(PluginBase): Result( title=f"Error: {account_name}", subtitle="Invalid secret or configuration", - icon_markup=icons.cancel, + icon_name="dialog-cancel-symbolic", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -485,7 +483,7 @@ class OTPPlugin(PluginBase): subtitle_markup=f"{display_name} • { time_display } remaining • Shift+Enter: remove", - icon_markup=icons.key, + icon_name="gtk-authentication-symbolic", action=lambda code=totp_code: self._copy_to_clipboard(code), relevance=1.0, plugin_name=self.display_name, @@ -505,7 +503,7 @@ class OTPPlugin(PluginBase): Result( title=f"No accounts found for '{query}'", subtitle="Use 'add <account> <secret>' to create new account", - icon_markup=icons.magnifier, + icon_name="edit-find-symbolic", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -516,7 +514,7 @@ class OTPPlugin(PluginBase): Result( title="Available commands:", subtitle="add <account> <secret> | remove <account> | qr <account>", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=0.4, plugin_name=self.display_name, @@ -533,7 +531,7 @@ class OTPPlugin(PluginBase): Result( title="Enter account name", subtitle="Usage: add <account_name> <secret>", - icon_markup=icons.question, + icon_name="dialog-question-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -545,7 +543,7 @@ class OTPPlugin(PluginBase): Result( title=f"To add '{account_name}':", subtitle=f"Type: add {account_name} <secret>", - icon_markup=icons.bulb, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -558,7 +556,7 @@ class OTPPlugin(PluginBase): Result( title="Base32 Secret Format:", subtitle="Example: add gmail JBSWY3DPEHPK3PXP", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -567,7 +565,7 @@ class OTPPlugin(PluginBase): Result( title="otpauth URI Format:", subtitle="Example: add github otpauth://totp/GitHub:user?secret=JBSWY3DPEHPK3PXP", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -576,7 +574,7 @@ class OTPPlugin(PluginBase): Result( title="Remove Account:", subtitle="Example: remove gmail", - icon_markup=icons.trash, + icon_name="trash", action=lambda: None, relevance=0.8, plugin_name=self.display_name, @@ -591,7 +589,7 @@ class OTPPlugin(PluginBase): Result( title="Scan QR Code", subtitle="Click to scan QR code from screen", - icon_markup=icons.connect, + icon_name="view-barcode-qr-symbolic", action=lambda: self._scan_qr_and_add_account(""), relevance=1.0, plugin_name=self.display_name, @@ -600,7 +598,7 @@ class OTPPlugin(PluginBase): Result( title="QR Scan Instructions:", subtitle="Use 'qr <account_name>' to specify account name", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -612,7 +610,7 @@ class OTPPlugin(PluginBase): Result( title=f"Scan QR Code for '{account_name}'", subtitle="Click to scan QR code from screen", - icon_markup=icons.connect, + icon_name="view-barcode-qr-symbolic", action=lambda name=account_name: self._scan_qr_and_add_account( name ), @@ -658,7 +656,7 @@ class OTPPlugin(PluginBase): Result( title="No OTP accounts to remove", subtitle="Use 'add <account> <secret>' to add accounts first", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -670,7 +668,7 @@ class OTPPlugin(PluginBase): Result( title="Select account to remove:", subtitle="Type: remove <account_name> to remove an account", - icon_markup=icons.trash, + icon_name="user-trash-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -697,7 +695,7 @@ class OTPPlugin(PluginBase): subtitle_markup=f"Press Enter to remove • { time_display } remaining", - icon_markup=icons.trash, + icon_name="user-trash-symbolic", action=lambda acc=acc_name: self._remove_account_and_refresh( acc ), @@ -716,7 +714,7 @@ class OTPPlugin(PluginBase): Result( title=f"Error: {acc_name}", subtitle="Press Enter to remove (Invalid secret)", - icon_markup=icons.trash, + icon_name="user-trash-symbolic", action=lambda acc=acc_name: self._remove_account_and_refresh( acc ), @@ -738,7 +736,7 @@ class OTPPlugin(PluginBase): Result( title=f"Account '{account_name}' not found", subtitle="Use 'remove' to see all available accounts", - icon_markup=icons.cancel, + icon_name="dialog-cancel-symbolic", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -756,7 +754,7 @@ class OTPPlugin(PluginBase): Result( title=f"Remove '{display_name}'?", subtitle="Press Enter to confirm removal", - icon_markup=icons.trash, + icon_name="user-trash-symbolic", action=lambda acc=account_name: self._remove_account_and_refresh(acc), relevance=1.0, plugin_name=self.display_name, @@ -777,7 +775,7 @@ class OTPPlugin(PluginBase): Result( title="Invalid Base32 secret", subtitle=result["error"], - icon_markup=icons.cancel, + icon_name="dialog-cancel-symbolic", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -800,7 +798,7 @@ class OTPPlugin(PluginBase): subtitle=f"OTP account added successfully (secret: { result['secret'][:4] }...)", - icon_markup=icons.check, + icon_name="emblem-ok-symbolic", action=lambda: self._trigger_refresh(), relevance=1.0, plugin_name=self.display_name, @@ -817,7 +815,7 @@ class OTPPlugin(PluginBase): Result( title="Error parsing otpauth URI", subtitle=result["error"], - icon_markup=icons.cancel, + icon_name="dialog-cancel-symbolic", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -843,7 +841,7 @@ class OTPPlugin(PluginBase): Result( title=f"✓ Added '{display_name}'", subtitle="OTP account added from URI", - icon_markup=icons.check, + icon_name="emblem-ok-symbolic", action=lambda: self._trigger_refresh(), relevance=1.0, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/password.py b/modules/launcher/plugins/password.py index 18f1f65..1551b20 100644 --- a/modules/launcher/plugins/password.py +++ b/modules/launcher/plugins/password.py @@ -8,8 +8,6 @@ from pathlib import Path from typing import Dict, List, Optional from fabric.utils import get_relative_path - -import utils.icons as icons from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -242,7 +240,7 @@ class PasswordPlugin(PluginBase): Result( title="No passwords stored", subtitle="Use 'pass add <name> <password>' to add your first password", - icon_markup=icons.key, + icon_name="password-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -253,7 +251,7 @@ class PasswordPlugin(PluginBase): Result( title="Available commands:", subtitle="add <name> <password> | remove <name> | <name> (to search)", - icon_markup=icons.info, + icon_name="info", action=lambda: None, relevance=0.9, plugin_name=self.display_name, @@ -284,7 +282,7 @@ class PasswordPlugin(PluginBase): Result( title=title, subtitle=subtitle, - icon_markup=icons.key, + icon_name="key", action=lambda n=name: self._copy_password_to_clipboard(n), relevance=1.0, plugin_name=self.display_name, @@ -311,7 +309,7 @@ class PasswordPlugin(PluginBase): Result( title="Add Password - Invalid format", subtitle="Usage: add <name> <password> [description]", - icon_markup=icons.cancel, + icon_name="cancel", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -330,7 +328,7 @@ class PasswordPlugin(PluginBase): Result( title=f"Update password for '{name}'?", subtitle="Password already exists. Click to update it.", - icon_markup=icons.key, + icon_name="key", action=lambda: self._add_password_action( name, password, description, update=True ), @@ -345,7 +343,7 @@ class PasswordPlugin(PluginBase): title=f"Add password for '{name}'", subtitle="Click to save password" + (f" | {description}" if description else ""), - icon_markup=icons.plus, + icon_name="plus", action=lambda: self._add_password_action( name, password, description ), @@ -367,7 +365,7 @@ class PasswordPlugin(PluginBase): Result( title="Remove Password - Invalid format", subtitle="Usage: remove <name>", - icon_markup=icons.cancel, + icon_name="cancel", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -383,7 +381,7 @@ class PasswordPlugin(PluginBase): Result( title=f"Password '{name}' not found", subtitle="Check the name and try again", - icon_markup=icons.cancel, + icon_name="cancel", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -395,7 +393,7 @@ class PasswordPlugin(PluginBase): Result( title=f"Remove password '{name}'?", subtitle="Click to confirm deletion (this cannot be undone)", - icon_markup=icons.trash, + icon_name="trash", action=lambda: self._remove_password_action(name), relevance=1.0, plugin_name=self.display_name, @@ -420,7 +418,7 @@ class PasswordPlugin(PluginBase): Result( title=f"No passwords found matching '{query}'", subtitle="Try a different search term or use 'pass' to see all passwords", - icon_markup=icons.magnifier, + icon_name="magnifier", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -462,7 +460,7 @@ class PasswordPlugin(PluginBase): Result( title=title, subtitle=subtitle, - icon_markup=icons.key, + icon_name="key", action=lambda n=name: self._copy_password_to_clipboard(n), relevance=get_relevance(name), plugin_name=self.display_name, diff --git a/modules/launcher/plugins/power.py b/modules/launcher/plugins/power.py index ffdc919..38b9cd8 100644 --- a/modules/launcher/plugins/power.py +++ b/modules/launcher/plugins/power.py @@ -1,8 +1,6 @@ from typing import List from fabric.utils import exec_shell_command_async - -import utils.icons as icons from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -19,27 +17,27 @@ class PowerPlugin(PluginBase): self.commands = { "shutdown": { "description": "Shutdown the system", - "icon": icons.shutdown, + "icon": "system-shutdown-symbolic", "action": self.shutdown, }, "restart": { "description": "Restart the system", - "icon": icons.reboot, + "icon": "system-reboot-symbolic", "action": self.restart, }, "lock": { "description": "Lock the screen", - "icon": icons.lock, + "icon": "system-lock-screen-symbolic", "action": self.lock, }, "suspend": { "description": "Suspend the system", - "icon": icons.suspend, + "icon": "system-suspend-symbolic", "action": self.suspend, }, "logout": { "description": "Logout from current session", - "icon": icons.logout, + "icon": "system-log-out-symbolic", "action": self.logout, }, } @@ -63,7 +61,7 @@ class PowerPlugin(PluginBase): result = Result( title=cmd.capitalize(), subtitle=info["description"], - icon_markup=info["icon"], + icon_name=info["icon"], action=info["action"], relevance=1.0, plugin_name=self.display_name, @@ -77,7 +75,7 @@ class PowerPlugin(PluginBase): result = Result( title=cmd.capitalize(), subtitle=info["description"], - icon_markup=info["icon"], + icon_name=info["icon"], action=info["action"], relevance=1.0 if query == cmd else 0.7, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/reminders.py b/modules/launcher/plugins/reminders.py index 947f1bd..ded8c20 100644 --- a/modules/launcher/plugins/reminders.py +++ b/modules/launcher/plugins/reminders.py @@ -4,7 +4,6 @@ import threading from datetime import datetime, timedelta from typing import Dict, List, Optional -import utils.icons as icons from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -258,7 +257,7 @@ class RemindersPlugin(PluginBase): subtitle=f"Active reminders: { active_count } | Usage: remind 5m Take a break", - icon_markup=icons.timer_on, + icon_name="alarm-timer", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -279,7 +278,7 @@ class RemindersPlugin(PluginBase): Result( title=example, subtitle=desc, - icon_markup=icons.timer_on, + icon_name="alarm-timer", action=lambda: None, relevance=0.8, plugin_name=self.display_name, @@ -296,7 +295,7 @@ class RemindersPlugin(PluginBase): Result( title="No Active Reminders", subtitle="Use 'remind 5m message' to set a reminder", - icon_markup=icons.timer_off, + icon_name="timer-off", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -314,7 +313,7 @@ class RemindersPlugin(PluginBase): Result( title=f"#{reminder.id}: {reminder.message}", subtitle=f"In {time_remaining} (at {target_time})", - icon_markup=icons.timer_on, + icon_name="alarm-timer", action=lambda rid=reminder.id: self._cancel_reminder(rid), relevance=1.0, plugin_name=self.display_name, @@ -334,7 +333,7 @@ class RemindersPlugin(PluginBase): Result( title=f"Cancelled {count} Reminders", subtitle="All active reminders have been cancelled", - icon_markup=icons.timer_off, + icon_name="timer-off", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -351,7 +350,7 @@ class RemindersPlugin(PluginBase): Result( title=f"Cancelled Reminder #{reminder_id}", subtitle="Reminder has been cancelled", - icon_markup=icons.timer_off, + icon_name="timer-off", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -363,7 +362,7 @@ class RemindersPlugin(PluginBase): Result( title="Reminder Not Found", subtitle=f"No reminder with ID #{reminder_id}", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -375,7 +374,7 @@ class RemindersPlugin(PluginBase): Result( title="Invalid Reminder ID", subtitle="Please provide a valid reminder ID number", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -407,7 +406,7 @@ class RemindersPlugin(PluginBase): subtitle=f"Will remind in {time_remaining} (at { target_time_str })", - icon_markup=icons.timer_on, + icon_name="alarm-timer", action=lambda ts=time_str, msg=message: self._create_and_confirm_reminder( ts, msg ), @@ -425,7 +424,7 @@ class RemindersPlugin(PluginBase): Result( title="Time is in the Past", subtitle="Please specify a future time", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=0.5, plugin_name=self.display_name, @@ -438,7 +437,7 @@ class RemindersPlugin(PluginBase): Result( title="Invalid Time Format", subtitle="Use formats like: 5m, 30s, 2h, 14:30, or '5 minutes'", - icon_markup=icons.alert, + icon_name="alert", action=lambda: None, relevance=0.5, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/screencapture.py b/modules/launcher/plugins/screencapture.py index 8cbea99..05253b6 100644 --- a/modules/launcher/plugins/screencapture.py +++ b/modules/launcher/plugins/screencapture.py @@ -2,10 +2,8 @@ import subprocess from typing import List from fabric.utils import get_relative_path - from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result -from utils.icons import screenrecord, screenshots, ssfull, ssregion, stop class ScreencapturePlugin(PluginBase): @@ -17,7 +15,6 @@ class ScreencapturePlugin(PluginBase): super().__init__() self.display_name = "Screencapture" self.description = "Take screenshots and screen recordings" - # Path to the screen-capture script self.script_path = get_relative_path("../../../scripts/screen-capture.sh") def initialize(self): @@ -121,290 +118,290 @@ class ScreencapturePlugin(PluginBase): "screenshot": ( "Take Screenshot (eDP-1)", "Capture the main display", - ssfull, + "camera-photo-symbolic", lambda: self._run_script("screenshot", "eDP-1"), ), "ss": ( "Take Screenshot (eDP-1)", "Capture the main display", - ssfull, + "camera-photo-symbolic", lambda: self._run_script("screenshot", "eDP-1"), ), "screenshot-region": ( "Take Region Screenshot", "Capture a selected region", - ssregion, + "camera-photo-symbolic", lambda: self._run_script("screenshot", "selection"), ), "ss-region": ( "Take Region Screenshot", "Capture a selected region", - ssregion, + "camera-photo-symbolic", lambda: self._run_script("screenshot", "selection"), ), "screenshot-both": ( "Take Screenshot (Both Displays)", "Capture both displays combined", - screenshots, + "video-joined-displays-symbolic", lambda: self._run_script("screenshot", "both"), ), "ss-both": ( "Take Screenshot (Both Displays)", "Capture both displays combined", - screenshots, + "video-joined-displays-symbolic", lambda: self._run_script("screenshot", "both"), ), "screenshot-hdmi": ( "Take Screenshot (HDMI-A-1)", "Capture HDMI display", - ssfull, + "video-display-symbolic", lambda: self._run_script("screenshot", "HDMI-A-1"), ), "ss-hdmi": ( "Take Screenshot (HDMI-A-1)", "Capture HDMI display", - ssfull, + "video-display-symbolic", lambda: self._run_script("screenshot", "HDMI-A-1"), ), # Recording commands (with audio) "record": ( "Start Recording (eDP-1)", "Record the main display with audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record", "eDP-1"), ), "rec": ( "Start Recording (eDP-1)", "Record the main display with audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record", "eDP-1"), ), "record-region": ( "Start Region Recording", "Record a selected region with audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record", "selection"), ), "rec-region": ( "Start Region Recording", "Record a selected region with audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record", "selection"), ), "record-hdmi": ( "Start Recording (HDMI-A-1)", "Record HDMI display with audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record", "HDMI-A-1"), ), "rec-hdmi": ( "Start Recording (HDMI-A-1)", "Record HDMI display with audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record", "HDMI-A-1"), ), # Recording commands (no audio) "record-noaudio": ( "Start Recording No Audio (eDP-1)", "Record the main display without audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-noaudio", "eDP-1"), ), "rec-noaudio": ( "Start Recording No Audio (eDP-1)", "Record the main display without audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-noaudio", "eDP-1"), ), "record-noaudio-region": ( "Start Region Recording No Audio", "Record a selected region without audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-noaudio", "selection"), ), "rec-noaudio-region": ( "Start Region Recording No Audio", "Record a selected region without audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-noaudio", "selection"), ), "record-noaudio-hdmi": ( "Start Recording No Audio (HDMI-A-1)", "Record HDMI display without audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-noaudio", "HDMI-A-1"), ), "rec-noaudio-hdmi": ( "Start Recording No Audio (HDMI-A-1)", "Record HDMI display without audio", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-noaudio", "HDMI-A-1"), ), # High-quality recording commands "record-hq": ( "Start HQ Recording (eDP-1)", "High-quality recording for YouTube", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-hq", "eDP-1"), ), "rec-hq": ( "Start HQ Recording (eDP-1)", "High-quality recording for YouTube", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-hq", "eDP-1"), ), "record-hq-region": ( "Start HQ Region Recording", "High-quality region recording", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-hq", "selection"), ), "rec-hq-region": ( "Start HQ Region Recording", "High-quality region recording", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-hq", "selection"), ), "record-hq-hdmi": ( "Start HQ Recording (HDMI-A-1)", "High-quality HDMI recording", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-hq", "HDMI-A-1"), ), "rec-hq-hdmi": ( "Start HQ Recording (HDMI-A-1)", "High-quality HDMI recording", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-hq", "HDMI-A-1"), ), # GIF recording commands "record-gif": ( "Start GIF Recording (eDP-1)", "Record as optimized GIF", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-gif", "eDP-1"), ), "rec-gif": ( "Start GIF Recording (eDP-1)", "Record as optimized GIF", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-gif", "eDP-1"), ), "record-gif-region": ( "Start GIF Region Recording", "Record selected region as GIF", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-gif", "selection"), ), "rec-gif-region": ( "Start GIF Region Recording", "Record selected region as GIF", - screenrecord, + "media-record-symbolic", lambda: self._run_script("record-gif", "selection"), ), # Control commands "stop": ( "Stop Recording", "Stop the current screen recording", - stop, + "media-playback-stop-symbolic", lambda: self._run_script("record", "stop"), ), # Conversion commands "convert-webm": ( "Convert Latest to WebM", "Convert latest MKV recording to WebM format", - screenrecord, + "video-x-generic-symbolic", lambda: self._run_script("convert", "webm"), ), "conv-webm": ( "Convert Latest to WebM", "Convert latest MKV recording to WebM format", - screenrecord, + "video-x-generic-symbolic", lambda: self._run_script("convert", "webm"), ), "convert-iphone": ( "Convert Latest for iPhone", "Convert latest MKV recording for iPhone compatibility", - screenrecord, + "video-x-generic-symbolic", lambda: self._run_script("convert", "iphone"), ), "conv-iphone": ( "Convert Latest for iPhone", "Convert latest MKV recording for iPhone compatibility", - screenrecord, + "video-x-generic-symbolic", lambda: self._run_script("convert", "iphone"), ), "convert-youtube": ( "Convert Latest for YouTube", "Convert latest recording for YouTube upload", - screenrecord, + "video-x-generic-symbolic", lambda: self._run_script("convert", "youtube"), ), "conv-youtube": ( "Convert Latest for YouTube", "Convert latest recording for YouTube upload", - screenrecord, + "video-x-generic-symbolic", lambda: self._run_script("convert", "youtube"), ), "convert-gif": ( "Convert Latest to GIF", "Convert latest recording to GIF format", - screenrecord, + "image-x-generic-symbolic", lambda: self._run_script("convert", "gif"), ), "conv-gif": ( "Convert Latest to GIF", "Convert latest recording to GIF format", - screenrecord, + "image-x-generic-symbolic", lambda: self._run_script("convert", "gif"), ), # File-based conversion commands (these will be handled specially) "convert-webm-file": ( "Convert File to WebM", "Type filename after command (e.g., convert-webm-file video.mkv)", - screenrecord, + "video-x-generic-symbolic", None, # Will be handled in query method ), "conv-webm-file": ( "Convert File to WebM", "Type filename after command (e.g., conv-webm-file video.mkv)", - screenrecord, + "video-x-generic-symbolic", None, # Will be handled in query method ), "convert-iphone-file": ( "Convert File for iPhone", "Type filename after command (e.g., convert-iphone-file video.mkv)", - screenrecord, + "video-x-generic-symbolic", None, # Will be handled in query method ), "conv-iphone-file": ( "Convert File for iPhone", "Type filename after command (e.g., conv-iphone-file video.mkv)", - screenrecord, + "video-x-generic-symbolic", None, # Will be handled in query method ), "convert-youtube-file": ( "Convert File for YouTube", "Type filename after command (e.g., convert-youtube-file video.mkv)", - screenrecord, + "video-x-generic-symbolic", None, # Will be handled in query method ), "conv-youtube-file": ( "Convert File for YouTube", "Type filename after command (e.g., conv-youtube-file video.mkv)", - screenrecord, + "video-x-generic-symbolic", None, # Will be handled in query method ), "convert-gif-file": ( "Convert File to GIF", "Type filename after command (e.g., convert-gif-file video.mkv)", - screenrecord, + "image-x-generic-symbolic", None, # Will be handled in query method ), "conv-gif-file": ( "Convert File to GIF", "Type filename after command (e.g., conv-gif-file video.mkv)", - screenrecord, + "image-x-generic-symbolic", None, # Will be handled in query method ), } @@ -415,7 +412,7 @@ class ScreencapturePlugin(PluginBase): return Result( title=title, subtitle=subtitle, - icon_markup=icon, + icon_name=icon, action=action, relevance=1.0, plugin_name=self.display_name, @@ -424,7 +421,7 @@ class ScreencapturePlugin(PluginBase): return Result( title=title, subtitle=subtitle, - icon_markup=icon, + icon_name=icon, action=lambda: None, # No action for instruction relevance=1.0, plugin_name=self.display_name, @@ -465,7 +462,11 @@ class ScreencapturePlugin(PluginBase): Result( title=f"Convert {file_param} to {format_type.upper()}", subtitle=f"Convert specified file to {format_type} format", - icon_markup=screenrecord, + icon_name=( + "video-x-generic-symbolic" + if format_type != "gif" + else "image-x-generic-symbolic" + ), action=lambda fp=file_param, ft=format_type: self._run_script_with_file( ft, fp ), @@ -488,7 +489,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Stop Recording", subtitle="Stop the current screen recording", - icon_markup=stop, + icon_name="media-playback-stop-symbolic", action=lambda: self._run_script("record", "stop"), relevance=2.0, # Highest relevance to appear at top plugin_name=self.display_name, @@ -501,7 +502,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Take Screenshot", subtitle="Capture the entire screen (eDP-1)", - icon_markup=ssfull, + icon_name="camera-photo-symbolic", action=lambda: self._run_script("screenshot", "eDP-1"), relevance=1.0, plugin_name=self.display_name, @@ -509,7 +510,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Take Region Screenshot", subtitle="Capture a selected region", - icon_markup=ssregion, + icon_name="camera-photo-symbolic", action=lambda: self._run_script("screenshot", "selection"), relevance=0.9, plugin_name=self.display_name, @@ -517,7 +518,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Take Screenshot (Both Displays)", subtitle="Capture both displays combined", - icon_markup=screenshots, + icon_name="video-joined-displays-symbolic", action=lambda: self._run_script("screenshot", "both"), relevance=0.8, plugin_name=self.display_name, @@ -525,7 +526,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Take Screenshot (HDMI-A-1)", subtitle="Capture HDMI display", - icon_markup=ssfull, + icon_name="video-display-symbolic", action=lambda: self._run_script("screenshot", "HDMI-A-1"), relevance=0.7, plugin_name=self.display_name, @@ -539,7 +540,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start Recording (eDP-1)", subtitle="Record the main display with audio", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record", "eDP-1"), relevance=0.7, plugin_name=self.display_name, @@ -547,7 +548,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start Region Recording", subtitle="Record a selected region", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record", "selection"), relevance=0.6, plugin_name=self.display_name, @@ -555,7 +556,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start Recording (HDMI-A-1)", subtitle="Record HDMI display with audio", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record", "HDMI-A-1"), relevance=0.5, plugin_name=self.display_name, @@ -569,7 +570,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start Recording No Audio (eDP-1)", subtitle="Record the main display without audio", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-noaudio", "eDP-1"), relevance=0.65, plugin_name=self.display_name, @@ -577,7 +578,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start Region Recording No Audio", subtitle="Record a selected region without audio", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-noaudio", "selection"), relevance=0.55, plugin_name=self.display_name, @@ -585,7 +586,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start Recording No Audio (HDMI-A-1)", subtitle="Record HDMI display without audio", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-noaudio", "HDMI-A-1"), relevance=0.45, plugin_name=self.display_name, @@ -599,7 +600,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start HQ Recording (eDP-1)", subtitle="High-quality recording for YouTube", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-hq", "eDP-1"), relevance=0.4, plugin_name=self.display_name, @@ -607,7 +608,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start HQ Region Recording", subtitle="High-quality region recording", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-hq", "selection"), relevance=0.3, plugin_name=self.display_name, @@ -615,7 +616,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start HQ Recording (HDMI-A-1)", subtitle="High-quality HDMI recording", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-hq", "HDMI-A-1"), relevance=0.2, plugin_name=self.display_name, @@ -629,7 +630,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start GIF Recording (eDP-1)", subtitle="Record as optimized GIF", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-gif", "eDP-1"), relevance=0.1, plugin_name=self.display_name, @@ -637,7 +638,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Start GIF Region Recording", subtitle="Record selected region as GIF", - icon_markup=screenrecord, + icon_name="media-record-symbolic", action=lambda: self._run_script("record-gif", "selection"), relevance=0.05, plugin_name=self.display_name, @@ -651,7 +652,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Convert Latest to WebM", subtitle="Convert latest MKV recording to WebM format", - icon_markup=screenrecord, + icon_name="video-x-generic-symbolic", action=lambda: self._run_script("convert", "webm"), relevance=0.01, plugin_name=self.display_name, @@ -659,7 +660,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Convert Latest for iPhone", subtitle="Convert latest MKV recording for iPhone compatibility", - icon_markup=screenrecord, + icon_name="video-x-generic-symbolic", action=lambda: self._run_script("convert", "iphone"), relevance=0.01, plugin_name=self.display_name, @@ -667,7 +668,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Convert Latest for YouTube", subtitle="Convert latest recording for YouTube upload", - icon_markup=screenrecord, + icon_name="video-x-generic-symbolic", action=lambda: self._run_script("convert", "youtube"), relevance=0.01, plugin_name=self.display_name, @@ -675,7 +676,7 @@ class ScreencapturePlugin(PluginBase): Result( title="Convert Latest to GIF", subtitle="Convert latest recording to GIF format", - icon_markup=screenrecord, + icon_name="image-x-generic-symbolic", action=lambda: self._run_script("convert", "gif"), relevance=0.01, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/system.py b/modules/launcher/plugins/system.py index c8bb04d..bc7dba0 100644 --- a/modules/launcher/plugins/system.py +++ b/modules/launcher/plugins/system.py @@ -5,10 +5,8 @@ import threading import time from typing import List, Set, Union -from fabric.utils import exec_shell_command_async - import config.data as data -import utils.icons as icons +from fabric.utils import exec_shell_command_async from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -91,7 +89,6 @@ class SystemPlugin(PluginBase): current_time - self._last_bin_update > self._bin_update_interval or not self._bin_cache ): - if not self._cache_building: self._cache_building = True self._cache_thread = threading.Thread( @@ -213,7 +210,7 @@ class SystemPlugin(PluginBase): result = Result( title=display_command, subtitle=f"Execute: {display_command}", - icon_markup=icons.terminal, + icon_name="terminal", action=self._create_action(command_to_execute), relevance=relevance, plugin_name=self.display_name, diff --git a/modules/launcher/plugins/tmux.py b/modules/launcher/plugins/tmux.py index ed9c3b6..5c93fad 100644 --- a/modules/launcher/plugins/tmux.py +++ b/modules/launcher/plugins/tmux.py @@ -3,10 +3,8 @@ import threading import time from typing import List -from fabric.utils import exec_shell_command_async - import config.data as data -import utils.icons as icons +from fabric.utils import exec_shell_command_async from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -24,13 +22,15 @@ class TmuxPlugin(PluginBase): # Cache for sessions to avoid repeated subprocess calls self._sessions_cache = [] self._last_cache_update = 0 - self._cache_ttl = 10 # Cache sessions for 10 seconds (increased from 2) + # Cache sessions for 10 seconds (increased from 2) + self._cache_ttl = 10 # Threading for auto-refresh - only when actively used self.refresh_thread = None self.stop_refresh = threading.Event() self._last_query_time = 0 - self._active_refresh_timeout = 30 # Stop refreshing after 30 seconds of inactivity + # Stop refreshing after 30 seconds of inactivity + self._active_refresh_timeout = 30 def initialize(self): """Initialize the tmux plugin.""" @@ -67,7 +67,7 @@ class TmuxPlugin(PluginBase): self._sessions_cache = self._get_tmux_sessions() self._last_cache_update = current_time - self.stop_refresh.wait(5) + self.stop_refresh.wait(5) except Exception as e: print(f"TmuxPlugin: Error in refresh thread: {e}") self.stop_refresh.wait(10) # Wait longer on error @@ -166,7 +166,7 @@ class TmuxPlugin(PluginBase): return Result( title=f"Attach to '{session_name}'", subtitle=f"Connect to tmux session: {session_name}", - icon_markup=icons.terminal, + icon_name="terminal", action=lambda: self._attach_to_session(session_name), relevance=0.9, data={"type": "attach", "session": session_name}, @@ -177,8 +177,10 @@ class TmuxPlugin(PluginBase): display_name = session_name if session_name else "new session" return Result( title=f"Create '{display_name}'", - subtitle=f"Create new tmux session{f': {session_name}' if session_name else ''}", - icon_markup=icons.plus, + subtitle=f"Create new tmux session{ + f': {session_name}' if session_name else '' + }", + icon_name="plus", action=lambda: self._create_session(session_name), relevance=0.8, data={"type": "create", "session": session_name}, @@ -189,7 +191,7 @@ class TmuxPlugin(PluginBase): return Result( title=f"Kill '{session_name}'", subtitle=f"Terminate tmux session: {session_name}", - icon_markup=icons.trash, + icon_name="trash", action=lambda: self._kill_session(session_name), relevance=0.7, data={"type": "kill", "session": session_name}, @@ -200,7 +202,7 @@ class TmuxPlugin(PluginBase): return Result( title=f"Rename '{old_name}' to '{new_name}'", subtitle=f"Rename tmux session from {old_name} to {new_name}", - icon_markup=icons.config, + icon_name="config", action=lambda: self._rename_session(old_name, new_name), relevance=0.6, data={"type": "rename", "old_session": old_name, "new_session": new_name}, diff --git a/modules/launcher/plugins/wallpaper.py b/modules/launcher/plugins/wallpaper.py index 64deea1..bae0be5 100644 --- a/modules/launcher/plugins/wallpaper.py +++ b/modules/launcher/plugins/wallpaper.py @@ -8,12 +8,11 @@ import threading import time from typing import Dict, List, Optional -from fabric.utils.helpers import exec_shell_command_async from gi.repository import GdkPixbuf from PIL import Image import config.data as data -import utils.icons as icons +from fabric.utils.helpers import exec_shell_command_async from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -124,9 +123,10 @@ class WallpaperPlugin(PluginBase): """Clear the launcher search query and reset to trigger.""" try: # Try to access the launcher through the fabric Application - from fabric import Application from gi.repository import GLib + from fabric import Application + app = Application.get_default() if app and hasattr(app, "launcher"): @@ -467,16 +467,7 @@ class WallpaperPlugin(PluginBase): 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, - ] - ), + icon_name="media-playlist-shuffle-symbolic", action=lambda: self._set_random_wallpaper(), relevance=1.0, plugin_name=self.display_name, @@ -493,16 +484,7 @@ class WallpaperPlugin(PluginBase): 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, - ] - ), + icon_name="media-playlist-shuffle-symbolic", action=lambda: self._set_random_wallpaper(), relevance=0.9, plugin_name=self.display_name, @@ -552,10 +534,9 @@ class WallpaperPlugin(PluginBase): subtitle=f"Apply with {scheme_name} scheme • { status_text }", - icon_markup=icons.palette, - action=lambda c=hex_color, s=scheme: self._apply_hex_color_direct( - c, s - ), + 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={ @@ -575,7 +556,7 @@ class WallpaperPlugin(PluginBase): indicator_text }", subtitle="Matugen is enabled • Disable matugen to use hex colors", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -594,7 +575,7 @@ class WallpaperPlugin(PluginBase): subtitle=f"Apply with {scheme_name} scheme • { status_text }", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda c=hex_color, s=scheme: ( self._apply_hex_color_direct(c, s) if not matugen_enabled @@ -617,7 +598,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Hex Color (incomplete): {hex_color}", subtitle="Complete the 6-digit hex color to apply", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda: None, relevance=0.7, plugin_name=self.display_name, @@ -647,7 +628,7 @@ class WallpaperPlugin(PluginBase): subtitle=f"Generate and apply random color with { scheme_name } scheme • {status_text}", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda s=scheme: self._apply_hex_color_direct( self._generate_random_hex_color(), s ), @@ -667,7 +648,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Cannot Apply Random Color{indicator_text}", subtitle="Matugen is enabled • Disable matugen to use hex colors", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -683,7 +664,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Random Hex Color{indicator_text}", subtitle=f"Generate and apply random color • {status_text}", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda s=scheme: ( self._apply_hex_color_direct( self._generate_random_hex_color(), s @@ -707,7 +688,7 @@ class WallpaperPlugin(PluginBase): Result( title="Hex Color Commands", subtitle="Use: color #FF5733, hex #00FF00, color random", - icon_markup=icons.palette, + icon_name="color-picker-symbolic", action=lambda: None, relevance=0.8, plugin_name=self.display_name, @@ -761,7 +742,7 @@ class WallpaperPlugin(PluginBase): if not matugen_enabled else "" ), - icon_markup=icons.palette, + 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, @@ -785,7 +766,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Enable Matugen{indicator_text}", subtitle=f"Turn on dynamic color generation • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: self._set_matugen_state(True), relevance=0.9, plugin_name=self.display_name, @@ -802,7 +783,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Disable Matugen{indicator_text}", subtitle=f"Turn off dynamic color generation • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: self._set_matugen_state(False), relevance=0.9, plugin_name=self.display_name, @@ -824,7 +805,7 @@ class WallpaperPlugin(PluginBase): subtitle=f"Switch matugen to { 'enabled' if new_state else 'disabled' } • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: self._set_matugen_state(new_state), relevance=0.9, plugin_name=self.display_name, @@ -843,7 +824,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Enable Matugen{indicator_text}", subtitle=f"Turn on dynamic color generation • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: self._set_matugen_state(True), relevance=0.8, plugin_name=self.display_name, @@ -862,7 +843,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Disable Matugen{indicator_text}", subtitle=f"Turn off dynamic color generation • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: self._set_matugen_state(False), relevance=0.8, plugin_name=self.display_name, @@ -879,7 +860,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Toggle Matugen{indicator_text}", subtitle=f"Switch matugen state • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: self._set_matugen_state(not current_state), relevance=0.8, plugin_name=self.display_name, @@ -898,7 +879,7 @@ class WallpaperPlugin(PluginBase): indicator_text }", subtitle=f"Dynamic colors • {status_text}", - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: None, relevance=0.8, plugin_name=self.display_name, @@ -912,7 +893,7 @@ class WallpaperPlugin(PluginBase): Result( title=f"Wallpaper System Status{indicator_text}", subtitle=status_text, - icon_markup=icons.palette, + icon_name="color-management-symbolic", action=lambda: None, relevance=1.0, plugin_name=self.display_name, @@ -925,16 +906,7 @@ class WallpaperPlugin(PluginBase): 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, - ] - ), + icon_name="media-playlist-shuffle-symbolic", action=lambda: self._set_random_wallpaper(), relevance=0.9, plugin_name=self.display_name, @@ -981,7 +953,7 @@ class WallpaperPlugin(PluginBase): ' • ' + status_text if not query else '' }", icon=icon, - icon_markup=icons.wallpapers if not icon else None, + 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, diff --git a/modules/launcher/plugins/websearch.py b/modules/launcher/plugins/websearch.py index 575492a..80f992b 100644 --- a/modules/launcher/plugins/websearch.py +++ b/modules/launcher/plugins/websearch.py @@ -2,7 +2,6 @@ import subprocess import urllib.parse from typing import List -import utils.icons as icons from modules.launcher.plugin_base import PluginBase from modules.launcher.result import Result @@ -23,49 +22,49 @@ class WebSearchPlugin(PluginBase): "google": { "name": "Google", "url": "https://www.google.com/search?q={}", - "icon": icons.google, + "icon": "google-symbolic", "description": "Search with Google", }, "duckduckgo": { "name": "DuckDuckGo", "url": "https://duckduckgo.com/?q={}", - "icon": icons.duckduckgo, + "icon": "preferences-desktop-search", "description": "Search with DuckDuckGo (privacy-focused)", }, "youtube": { "name": "YouTube", "url": "https://www.youtube.com/results?search_query={}", - "icon": icons.youtube, + "icon": "youtube-symbolic", "description": "Search videos on YouTube", }, "github": { "name": "GitHub", "url": "https://github.com/search?q={}", - "icon": icons.github, + "icon": "github-desktop-symbolic", "description": "Search repositories on GitHub", }, "stackoverflow": { "name": "Stack Overflow", "url": "https://stackoverflow.com/search?q={}", - "icon": icons.stackoverflow, + "icon": "stack", "description": "Search programming questions on Stack Overflow", }, "wikipedia": { "name": "Wikipedia", "url": "https://en.wikipedia.org/wiki/Special:Search?search={}", - "icon": icons.wikipedia, + "icon": "search-menus-symbolic", "description": "Search articles on Wikipedia", }, "reddit": { "name": "Reddit", "url": "https://www.reddit.com/search/?q={}", - "icon": icons.reddit, + "icon": "reddit-symbolic", "description": "Search discussions on Reddit", }, "linkedin": { "name": "LinkedIn", "url": "https://www.linkedin.com/search/results/all/?keywords={}", - "icon": icons.linkedin, + "icon": "link", "description": "Search professionals and jobs on LinkedIn", }, } @@ -166,7 +165,7 @@ class WebSearchPlugin(PluginBase): return Result( title=f"Open {url}", subtitle="Open this URL in your default browser", - icon_markup=icons.world, + icon_name="link", action=lambda u=url: self._open_url(u), relevance=1.0, plugin_name=self.display_name, @@ -190,7 +189,7 @@ class WebSearchPlugin(PluginBase): result = Result( title=engine_info["name"], subtitle=engine_info["description"], - icon_markup=engine_info["icon"], + icon_name=engine_info["icon"], action=lambda e=engine_id: self._show_engine_help(e), relevance=1.0 if engine_id == self.default_engine else 0.8, plugin_name=self.display_name, @@ -207,7 +206,7 @@ class WebSearchPlugin(PluginBase): return Result( title=f"Search '{query}' on {engine_info['name']}", subtitle=f"{engine_info['description']} - {query}", - icon_markup=engine_info["icon"], + icon_name=engine_info["icon"], action=lambda e=engine_id, q=query: self._perform_search(e, q), relevance=1.0 if engine_id == self.default_engine else 0.9, plugin_name=self.display_name, @@ -221,7 +220,7 @@ class WebSearchPlugin(PluginBase): return Result( title=f"{engine_info['name']} Search", subtitle=f"{engine_info['description']} - Type your search query", - icon_markup=engine_info["icon"], + icon_name=engine_info["icon"], action=lambda: None, # No action for info result relevance=1.0, plugin_name=self.display_name, diff --git a/modules/launcher/result_item.py b/modules/launcher/result_item.py index e18fd9e..baaaa43 100644 --- a/modules/launcher/result_item.py +++ b/modules/launcher/result_item.py @@ -62,7 +62,7 @@ class ResultItem(EventBox): icon_widget = Image(pixbuf=self.result.icon, name="result-item-icon") elif self.result.icon_name: icon_widget = Image( - icon_name=self.result.icon_name, icon_size=32, name="result-item-icon" + icon_name=self.result.icon_name, icon_size=48, name="result-item-icon" ) elif self.result.icon_markup: icon_widget = Label( @@ -71,8 +71,8 @@ class ResultItem(EventBox): else: # Default icon icon_widget = Image( - icon_name="application-x-executable", - icon_size=32, + icon_name="application-default-icon", + icon_size=48, name="result-item-icon", ) diff --git a/modules/launcher/trigger_config.py b/modules/launcher/trigger_config.py index cb67687..3a568c4 100644 --- a/modules/launcher/trigger_config.py +++ b/modules/launcher/trigger_config.py @@ -4,8 +4,6 @@ from typing import Any, Dict, List from fabric.utils import get_relative_path -import utils.icons as icons - class TriggerConfig: def __init__(self, config_path: str = None): @@ -29,7 +27,7 @@ class TriggerConfig: # Initialize settings with defaults default_settings = { "max_examples_shown": 2, - "default_icon": "apps", + "default_icon": "application-default-icon", "fallback_example_template": "{trigger} <search>", "config_version": "1.0", } @@ -45,10 +43,10 @@ class TriggerConfig: return examples def get_trigger_icon(self, trigger: str) -> str: - icon_name = self.launcher_config.get(trigger, {}).get( - "icon", self.settings.get("default_icon", "apps") + icon = self.launcher_config.get(trigger, {}).get( + "icon", self.settings.get("default_icon", "application-default-icon") ) - return getattr(icons, icon_name, icons.apps) + return icon def get_trigger_description(self, trigger: str) -> str: return self.launcher_config.get(trigger, {}).get( diff --git a/widgets/smartscrolled.py b/widgets/smartscrolled.py new file mode 100644 index 0000000..c1a48a8 --- /dev/null +++ b/widgets/smartscrolled.py @@ -0,0 +1,76 @@ +from gi.repository import Gtk, GLib + +from fabric.widgets.box import Box +from fabric.widgets.scrolledwindow import ScrolledWindow + + +class SmartScrolledBox(Box): + def __init__( + self, + max_height: int = 300, + spacing: int = 6, + **kwargs, + ): + super().__init__(orientation="v", spacing=spacing, **kwargs) + + self._max_height = max_height + + # The actual content box that holds children + self.content_box = Box(orientation="v", spacing=spacing) + + # Viewport → for scrolling non-scrollable widgets like Box + self.viewport = Gtk.Viewport() + self.viewport.add(self.content_box) + + # ScrolledWindow → wrapper for vertical scrolling + self.scroller = ScrolledWindow() + self.scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.scroller.add(self.viewport) + + self.add(self.scroller) + + # Initial size check + GLib.idle_add(self._apply_height_constraint) + + def append(self, widget: Gtk.Widget): + self.content_box.add(widget) + self._apply_height_constraint() + + def remove(self, widget: Gtk.Widget): + self.content_box.remove(widget) + self._apply_height_constraint() + + def clear(self): + for child in self.content_box.get_children(): + self.content_box.remove(child) + self._apply_height_constraint() + + def update_height(self): + """Manually trigger height update - useful when content is added externally.""" + self._apply_height_constraint() + + def force_height_constraint(self): + """Force height constraint immediately.""" + self._apply_height_constraint() + + def _apply_height_constraint(self): + """Apply height constraint immediately without delay.""" + _, nat_height = self.content_box.get_preferred_height() + if nat_height > self._max_height: + # Force height constraint and show scrollbar + self.set_size_request(-1, self._max_height) + self.scroller.set_size_request(-1, self._max_height) + self.scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) + else: + # Allow natural height, no size request + self.set_size_request(-1, -1) + self.scroller.set_size_request(-1, -1) + self.scroller.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) + + def get_vadjustment(self): + """Get the vertical adjustment from the scrolled window.""" + return self.scroller.get_vadjustment() + + def get_hadjustment(self): + """Get the horizontal adjustment from the scrolled window.""" + return self.scroller.get_hadjustment()