refactor(launcher): remove launcher module files

This commit is contained in:
sankalp committed 2026-07-06 19:49:49 +05:45
1 parent 023d0be7c8
commit 9c74b0f606
7 files changed
-2038

No files matched your search

-8
View File
@@ -1,8 +0,0 @@
"""
Plugin-based launcher module for Fabric.
Similar to Albert Launcher with extensible plugin system.
"""
from .main import Launcher
__all__ = ["Launcher"]
File diff suppressed because it is too large. Load diff
-172
View File
@@ -1,172 +0,0 @@
from abc import ABC, abstractmethod
from typing import List
from window.launcher.result import Result
class PluginBase(ABC):
"""
Abstract base class for launcher plugins.
All plugins must inherit from this class.
"""
def __init__(self):
self.name = self.__class__.__name__.lower()
self.display_name = self.__class__.__name__
self.description = "A launcher plugin"
self.version = "1.0.0"
self.enabled = True
self._triggers = [] # List of trigger keywords
@abstractmethod
def initialize(self):
"""
Initialize the plugin.
Called when the plugin is activated.
"""
pass
@abstractmethod
def cleanup(self):
"""
Cleanup the plugin.
Called when the plugin is deactivated.
"""
pass
@abstractmethod
def query(self, query_string: str) -> List[Result]:
"""
Process a search query and return results.
Args:
query_string: The search query from the user
Returns:
List of Result objects
"""
pass
def get_triggers(self) -> List[str]:
"""
Get list of trigger keywords for this plugin.
If the query starts with any of these, this plugin gets priority.
Returns:
List of trigger strings (e.g., ["calc", "=", "math"])
"""
return self._triggers
def set_triggers(self, triggers: List[str]):
"""
Set the trigger keywords for this plugin.
Args:
triggers: List of trigger keywords
"""
self._triggers = triggers
def handles_query(self, query_string: str) -> bool:
"""
Check if this plugin should handle the given query.
Args:
query_string: The search query
Returns:
True if this plugin should process the query
"""
if not self.enabled:
return False
# Check triggers
triggers = self.get_triggers()
if triggers:
query_lower = query_string.lower().strip()
return any(query_lower.startswith(trigger.lower()) for trigger in triggers)
# Default: handle all queries
return True
def get_active_trigger(self, query_string: str) -> str:
"""
Get the active trigger for the given query.
Args:
query_string: The search query
Returns:
The trigger keyword if found, empty string otherwise
"""
if not self.enabled:
return ""
triggers = self.get_triggers()
if triggers:
query_lower = query_string.lower().strip()
# Sort triggers by length (longest first) to match more specific triggers first
sorted_triggers = sorted(triggers, key=len, reverse=True)
for trigger in sorted_triggers:
trigger_lower = trigger.lower()
# Exact match with trigger (including space if present)
if query_lower.startswith(trigger_lower):
return trigger
# Match trigger word followed by space
trigger_word = trigger.strip().lower()
if (
query_lower.startswith(trigger_word + " ")
or query_lower == trigger_word
):
return trigger
return ""
def query_triggered(self, query_string: str, trigger: str) -> List[Result]:
"""
Process a triggered query (when plugin is in sticky mode).
Default implementation removes trigger and calls query().
Args:
query_string: The full query string including trigger
trigger: The trigger that activated this plugin
Returns:
List of Result objects
"""
# Remove trigger from query and process remaining text
remaining_query = query_string[len(trigger) :].strip()
return self.query(remaining_query)
def get_config(self) -> dict:
"""
Get plugin configuration.
Returns:
Dictionary of configuration options
"""
return {
"name": self.name,
"display_name": self.display_name,
"description": self.description,
"version": self.version,
"enabled": self.enabled,
}
def set_config(self, config: dict):
"""
Set plugin configuration.
Args:
config: Dictionary of configuration options
"""
self.enabled = config.get("enabled", self.enabled)
def __str__(self):
return f"Plugin({self.name})"
def __repr__(self):
return self.__str__()
-177
View File
@@ -1,177 +0,0 @@
from fabric.utils import logger
import importlib
import importlib.util
import os
from typing import Dict, List, Type
from window.launcher.plugin_base import PluginBase
class PluginManager:
"""
Manages launcher plugins.
"""
def __init__(self):
self.plugins: Dict[str, PluginBase] = {}
self.plugin_classes: Dict[str, Type[PluginBase]] = {}
self.active_plugins: List[str] = []
# Load built-in plugins
self._load_builtin_plugins()
# Load external plugins
self._load_external_plugins()
# Activate default plugins
self._activate_default_plugins()
def _load_builtin_plugins(self):
"""Load built-in plugins from the plugins directory."""
plugins_dir = os.path.join(os.path.dirname(__file__), "plugins")
if not os.path.exists(plugins_dir):
return
for filename in os.listdir(plugins_dir):
if filename.endswith(".py") and not filename.startswith("_"):
plugin_name = filename[:-3] # Remove .py extension
self._load_plugin_from_file(plugins_dir, plugin_name)
def _load_external_plugins(self):
"""Load external plugins from user directory."""
# Could be implemented to load from ~/.config/launcher/plugins/
pass
def _load_plugin_from_file(self, plugins_dir: str, plugin_name: str):
"""Load a plugin from a Python file."""
try:
plugin_path = os.path.join(plugins_dir, f"{plugin_name}.py")
spec = importlib.util.spec_from_file_location(
f"window.launcher.plugins.{plugin_name}", plugin_path
)
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
# Add the module to sys.window to support relative imports
import sys
sys.modules[f"window.launcher.plugins.{plugin_name}"] = module
spec.loader.exec_module(module)
# Look for plugin class
for attr_name in dir(module):
attr = getattr(module, attr_name)
if (
isinstance(attr, type)
and issubclass(attr, PluginBase)
and attr != PluginBase
):
self.plugin_classes[plugin_name] = attr
break
except Exception as e:
logger.error(f"Failed to load plugin {plugin_name}: {e}")
def _activate_default_plugins(self):
"""Activate default plugins."""
default_plugins = [
"applications",
"calculator",
"clipboard",
"power",
"caffeine",
"screencapture",
"emoji",
"wallpaper",
"websearch",
]
for plugin_name in default_plugins:
self.activate_plugin(plugin_name)
def activate_plugin(self, plugin_name: str) -> bool:
"""Activate a plugin by name."""
if plugin_name in self.plugins:
# Already activated
return True
if plugin_name not in self.plugin_classes:
return False
try:
# Instantiate plugin
plugin_class = self.plugin_classes[plugin_name]
plugin_instance = plugin_class()
# Initialize plugin
plugin_instance.initialize()
# Store plugin
self.plugins[plugin_name] = plugin_instance
self.active_plugins.append(plugin_name)
return True
except Exception as e:
logger.error(f"Failed to activate plugin {plugin_name}: {e}")
return False
def deactivate_plugin(self, plugin_name: str) -> bool:
"""Deactivate a plugin by name."""
if plugin_name not in self.plugins:
return False
try:
# Cleanup plugin
plugin = self.plugins[plugin_name]
plugin.cleanup()
# Remove from active plugins
del self.plugins[plugin_name]
if plugin_name in self.active_plugins:
self.active_plugins.remove(plugin_name)
return True
except Exception as e:
logger.error(f"Failed to deactivate plugin {plugin_name}: {e}")
return False
def get_active_plugins(self) -> List[PluginBase]:
"""Get list of active plugin instances."""
return [
self.plugins[name] for name in self.active_plugins if name in self.plugins
]
def get_plugin_names(self) -> List[str]:
"""Get list of available plugin names."""
return list(self.plugin_classes.keys())
def get_active_plugin_names(self) -> List[str]:
"""Get list of active plugin names."""
return self.active_plugins.copy()
def reload_plugin(self, plugin_name: str) -> bool:
"""Reload a plugin."""
was_active = plugin_name in self.active_plugins
# Deactivate if active
if was_active:
self.deactivate_plugin(plugin_name)
# Remove from classes
if plugin_name in self.plugin_classes:
del self.plugin_classes[plugin_name]
# Reload from file
plugins_dir = os.path.join(os.path.dirname(__file__), "plugins")
self._load_plugin_from_file(plugins_dir, plugin_name)
# Reactivate if it was active
if was_active and plugin_name in self.plugin_classes:
return self.activate_plugin(plugin_name)
return plugin_name in self.plugin_classes
-52
View File
@@ -1,52 +0,0 @@
from dataclasses import dataclass
from typing import Any, Callable, Optional
from fabric.utils import GdkPixbuf, Gtk
@dataclass
class Result:
"""
Represents a search result that can be displayed and activated.
"""
# Display information
title: str
subtitle: str = ""
subtitle_markup: Optional[str] = None # Pango markup for subtitle
description: str = ""
icon: Optional[GdkPixbuf.Pixbuf] = None
icon_name: Optional[str] = None
icon_markup: Optional[str] = None
# Behavior
action: Optional[Callable[[], Any]] = None
relevance: float = 0.0
# Custom widget support
custom_widget: Optional[Gtk.Widget] = None
# Metadata
plugin_name: str = ""
data: Optional[dict] = None
def activate(self):
"""Activate this result (execute its action)."""
if self.action:
return self.action()
else:
raise NotImplementedError("No action defined for this result")
def __post_init__(self):
"""Post-initialization processing."""
# Ensure relevance is within valid range
self.relevance = max(0.0, min(1.0, self.relevance))
# Set default data if None
if self.data is None:
self.data = {}
def __str__(self):
return f"Result(title='{self.title}', relevance={self.relevance})"
def __repr__(self):
return self.__str__()
-161
View File
@@ -1,161 +0,0 @@
from fabric.core.service import Signal
from fabric.widgets.box import Box
from fabric.widgets.eventbox import EventBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from window.launcher.result import Result
class ResultItem(EventBox):
"""
Widget for displaying a single search result.
"""
# Signals
@Signal
def clicked(self, index: int) -> None:
"""Emitted when result is clicked."""
pass
@Signal
def hovered(self, index: int) -> None:
"""Emitted when result is hovered."""
pass
def __init__(
self, result: Result, selected: bool = False, index: int = 0, **kwargs
):
super().__init__(name="launcher-result-item", **kwargs)
self.result = result
self._selected = selected
self.index = index
# Setup UI
self._setup_ui()
# Connect signals
self.connect("button-press-event", self._on_button_press)
self.connect("enter-notify-event", self._on_enter)
self.connect("leave-notify-event", self._on_leave)
# Set initial selection state
self.set_selected(selected)
def _setup_ui(self):
"""Setup the result item UI."""
# Main container
main_box = Box(
name="result-item-main",
orientation="h",
spacing=12,
h_align="fill",
v_align="center",
)
self.add(main_box)
# Icon
if self.result.icon:
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=48, name="result-item-icon"
)
elif self.result.icon_markup:
icon_widget = Label(
markup=self.result.icon_markup, name="launcher-icon-label"
)
else:
# Default icon
icon_widget = Image(
icon_name="application-default-icon",
icon_size=48,
name="result-item-icon",
)
main_box.add(icon_widget)
# Text container
text_box = Box(
name="result-item-text",
orientation="v",
spacing=2,
h_expand=True,
v_align="center",
)
main_box.add(text_box)
# Title
title_label = Label(
label=self.result.title,
name="result-item-title",
h_align="start",
v_align="center",
ellipsize="end",
)
text_box.add(title_label)
# Subtitle (if present)
if self.result.subtitle or self.result.subtitle_markup:
if self.result.subtitle_markup:
# Use markup for subtitle (supports Pango markup)
subtitle_label = Label(
markup=self.result.subtitle_markup,
name="result-item-subtitle",
h_align="start",
v_align="center",
ellipsize="end",
)
else:
# Use plain text for subtitle
subtitle_label = Label(
label=self.result.subtitle,
name="result-item-subtitle",
h_align="start",
v_align="center",
ellipsize="end",
)
text_box.add(subtitle_label)
# Plugin name (small text)
if self.result.plugin_name:
plugin_label = Label(
label=f"via {self.result.plugin_name}",
name="result-item-plugin",
h_align="start",
v_align="center",
ellipsize="end",
)
text_box.add(plugin_label)
def set_selected(self, selected: bool):
"""Set the selection state of this result item."""
self._selected = selected
if selected:
self.add_style_class("selected")
else:
self.remove_style_class("selected")
def get_selected(self) -> bool:
"""Get the selection state of this result item."""
return self._selected
def _on_button_press(self, widget, event):
"""Handle button press events."""
if event.button == 1: # Left click
self.clicked.emit(self.index)
return True
return False
def _on_enter(self, widget, event):
"""Handle mouse enter events."""
# Emit hover signal to update selection
self.hovered.emit(self.index)
return False
def _on_leave(self, widget, event):
"""Handle mouse leave events."""
# Could be used for hover effects cleanup
return False
-60
View File
@@ -1,60 +0,0 @@
from typing import Any, Dict, List
import tomlkit
from utils.utils import toml_file
class TriggerConfig:
def __init__(self, config_path: str = None):
if config_path is None:
config_path = toml_file("launcher.toml")
self.config_path = config_path
config = {"launcher_config": {}, "settings": {}}
try:
with open(config_path, "r") as f:
data = tomlkit.load(f)
config["launcher_config"] = dict(data.get("launcher_config", {}))
config["settings"] = dict(data.get("settings", {}))
except FileNotFoundError:
pass
except Exception as e:
from fabric.utils import logger
logger.error(f"Error loading trigger config: {e}")
self.config = config
self.launcher_config = self.config.get("launcher_config", {})
# Initialize settings with defaults
default_settings = {
"max_examples_shown": 2,
"default_icon": "application-default-icon",
"fallback_example_template": "{trigger} <search>",
"config_version": "1.0",
}
self.settings = {**default_settings, **self.config.get("settings", {})}
def get_trigger_examples(self, trigger: str) -> List[str]:
examples = self.launcher_config.get(trigger, {}).get("examples", [])
if not examples:
template = self.settings.get(
"fallback_example_template", "{trigger} <search>"
)
examples = [template.format(trigger=trigger)]
return examples
def get_trigger_icon(self, trigger: str) -> str:
icon = self.launcher_config.get(trigger, {}).get(
"icon", self.settings.get("default_icon", "application-default-icon")
)
return icon
def get_trigger_description(self, trigger: str) -> str:
return self.launcher_config.get(trigger, {}).get(
"description", f"{trigger} - No description available"
)
def get_all_triggers(self) -> Dict[str, Dict[str, Any]]:
return self.launcher_config.copy()