-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Understand the design, structure, and technical implementation of Launcher.
- Architecture Overview
- Design Principles
- Project Structure
- Core Components
- Data Flow
- Technology Stack
- Design Patterns
- Module Dependencies
Launcher follows a modular, layered architecture that separates concerns and promotes maintainability:
┌─────────────────────────────────────────┐
│ User Interface Layer │
│ (GTK4/Adwaita Widgets) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Controller Layer │
│ (Event Handlers & Controllers) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Handler Layer │
│ (Input & Command Handlers) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Service Layer │
│ (Business Logic & Data Processing) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Data Layer │
│ (Database & File System Access) │
└─────────────────────────────────────────┘
- UI Layer: Only responsible for presentation
- Controller Layer: Handles user interactions
- Service Layer: Contains business logic
- Data Layer: Manages persistence
- Independent, reusable components
- Clear interfaces between modules
- Pluggable architecture for extensions
- Comprehensive type hints throughout
- Static type checking with MyPy
- Better IDE support and autocomplete
- Lazy loading of resources
- Caching strategies at multiple levels
- Background processing for heavy operations
- Optimized database queries
- Plugin/extension system
- Service registration pattern
- Dynamic class loading
- YAML-based configuration
launcher-app/
├── cloud/
│ └── ivanbotty/
│ ├── database/ # Data persistence
│ │ ├── sqlite3.py # SQLite wrapper with type hints
│ │ └── __init__.py
│ │
│ ├── utils/ # Shared utilities
│ │ ├── app_init.py # Application initialization
│ │ └── __init__.py
│ │
│ ├── Launcher/ # Main application
│ │ ├── app.py # Application class
│ │ ├── __main__.py # Entry point
│ │ │
│ │ ├── config/ # Configuration constants
│ │ │ └── constants.py # App-wide constants
│ │ │
│ │ ├── controller/ # Event controllers
│ │ │ ├── search_controller.py
│ │ │ ├── key_controller.py
│ │ │ └── click_controller.py
│ │ │
│ │ ├── handlers/ # Input handlers
│ │ │ ├── base_input_handler.py
│ │ │ ├── applications_handler.py
│ │ │ ├── math_handler.py
│ │ │ ├── command_handler.py
│ │ │ ├── ai_handler.py
│ │ │ ├── link_handler.py
│ │ │ └── extensions_handler.py
│ │ │
│ │ ├── helper/ # Helper utilities
│ │ │ ├── parser.py # Input parsing
│ │ │ ├── thread_manager.py
│ │ │ └── dynamic_loader.py
│ │ │
│ │ ├── models/ # Data models
│ │ │ ├── applications_model.py
│ │ │ └── extension_model.py
│ │ │
│ │ ├── services/ # Business logic
│ │ │ ├── applications_service.py
│ │ │ ├── extensions_service.py
│ │ │ ├── math_service.py
│ │ │ ├── command_service.py
│ │ │ └── ai_service.py
│ │ │
│ │ ├── widget/ # UI components
│ │ │ ├── window.py # Main window
│ │ │ ├── search_entry.py
│ │ │ ├── row.py # Application row
│ │ │ ├── preferences.py
│ │ │ ├── progress_bar.py
│ │ │ └── footer.py
│ │ │
│ │ └── resources/ # Assets & configs
│ │ ├── *.svg # Icon files
│ │ ├── appdata.xml # AppStream metadata
│ │ ├── extensions.yaml
│ │ └── wizard.yaml
│ │
│ └── Wizard/ # Welcome wizard
│ ├── app.py
│ ├── __main__.py
│ └── components/
│
├── tests/ # Test suite
│ ├── test_utils.py
│ ├── test_helpers.py
│ └── test_performance.py
│
├── cloud.ivanbotty.Launcher.yaml # Flatpak manifest
├── meson.build # Meson build config
├── pyproject.toml # Python project config
├── LICENSE # GPL-3.0-or-later
├── README.md # Project readme
└── CONTRIBUTING # Contribution guidelines
The main application class that:
- Initializes GTK application
- Sets up UI components
- Manages application lifecycle
- Handles signals and events
class LauncherApp(Adw.Application):
def __init__(self):
super().__init__(application_id="cloud.ivanbotty.Launcher")
# Initialize services, UI, etc.The primary UI container:
- Creates and manages UI layout
- Handles window events
- Coordinates between widgets
Purpose: Manage application discovery and search
Responsibilities:
- Scan desktop files from standard locations
- Parse
.desktopentries - Maintain application database
- Provide search functionality
- Resolve application icons
Key Methods:
def discover_applications(self) -> List[Application]
def search(self, query: str) -> List[Application]
def get_application(self, desktop_id: str) -> Optional[Application]
def launch_application(self, app: Application) -> NonePurpose: Manage extension lifecycle
Responsibilities:
- Load extension definitions from YAML
- Enable/disable extensions
- Track extension state
- Register extension services
Key Methods:
def load_extensions(self) -> List[Extension]
def enable_extension(self, extension_id: str) -> None
def disable_extension(self, extension_id: str) -> None
def get_enabled_extensions(self) -> List[Extension]Purpose: Evaluate mathematical expressions
Responsibilities:
- Parse math expressions
- Safely evaluate calculations
- Support functions and constants
- Format results
Key Methods:
def calculate(self, expression: str) -> float
def format_result(self, value: float) -> strAbstract base class for all input handlers:
class BaseInputHandler(ABC):
@abstractmethod
def can_handle(self, input_text: str) -> bool:
"""Check if this handler can process the input."""
pass
@abstractmethod
def handle(self, input_text: str) -> List[Result]:
"""Process input and return results."""
pass- ApplicationsHandler: Handles app search queries
- MathHandler: Processes mathematical expressions
- CommandHandler: Executes shell commands
- AIHandler: Processes AI queries
- LinkHandler: Manages web links
Manages search input and coordination:
- Receives user input
- Routes to appropriate handlers
- Aggregates results
- Updates UI
Handles keyboard interactions:
- Navigation (up/down arrows)
- Selection (Enter)
- Shortcuts (Ctrl+...)
Manages mouse interactions:
- Click events
- Hover effects
- Context menus
SQLite database wrapper with type hints:
class Database:
def __init__(self, db_path: str):
self.connection: sqlite3.Connection = ...
def execute(self, query: str, params: tuple = ()) -> sqlite3.Cursor:
"""Execute a query with parameters."""
pass
def get_preference(self, key: str, default: Any = None) -> Any:
"""Get a user preference."""
pass
def set_preference(self, key: str, value: Any) -> None:
"""Save a user preference."""
passData classes representing domain entities:
@dataclass
class Application:
desktop_id: str
name: str
description: str
icon: str
exec: str
categories: List[str]
@dataclass
class Extension:
id: str
name: str
enabled: bool
handler: str
service: strUser Input
↓
SearchEntry Widget
↓
SearchController
↓
Parser (determine input type)
↓
Router (select appropriate handler)
↓
Handler (ApplicationsHandler, MathHandler, etc.)
↓
Service (ApplicationsService, MathService, etc.)
↓
Data Layer (Database, File System)
↓
Results aggregated
↓
UI Update (display results)
User Selection (click or Enter)
↓
ClickController / KeyController
↓
Application Model
↓
ApplicationsService.launch_application()
↓
Gio.AppInfo.launch()
↓
Application Started
Application Startup
↓
ExtensionsService.load_extensions()
↓
Read extensions.yaml
↓
Parse extension definitions
↓
For each enabled extension:
↓
DynamicLoader.load_class_instance()
↓
Register handler and service
↓
Cache instance
↓
Extensions Ready
- Modern Python features
- Type hints and annotations
- Async/await support
- Modern UI toolkit
- Hardware-accelerated rendering
- Native Linux integration
- Wayland and X11 support
- GNOME design language
- Adaptive layouts
- Dark mode support
- Modern widgets
- Python bindings for GTK
- GObject introspection
- Full GTK4 access
- Embedded database
- User preferences storage
- Application cache
- Extension state
- Configuration files
- Extension definitions
- Resource manifests
- Build system
- Cross-platform support
- Fast incremental builds
- Sandboxed distribution
- Dependency management
- Cross-distro compatibility
- Code formatting
- Consistent style
- Line length: 100
- Code linting
- Style checking
- Error detection
- Static type checking
- Type hint validation
- Unit testing framework
- Test discovery
- Assertions and mocking
Services encapsulate business logic:
class ApplicationsService:
def __init__(self):
self._cache = {}
def search(self, query: str) -> List[Application]:
# Business logic here
passHandlers process specific input types:
class MathHandler(BaseInputHandler):
def can_handle(self, input_text: str) -> bool:
return self._is_math_expression(input_text)
def handle(self, input_text: str) -> List[Result]:
result = self.math_service.calculate(input_text)
return [Result(str(result))]GTK signals for event handling:
search_entry.connect("changed", self._on_search_changed)Dynamic class loading:
def load_class_instance(module_path: str, class_name: str):
module = importlib.import_module(module_path)
class_obj = getattr(module, class_name)
return class_obj()Single database connection:
class Database:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instancePluggable handlers for different input types:
handlers = [
ApplicationsHandler(),
MathHandler(),
CommandHandler(),
]
for handler in handlers:
if handler.can_handle(input_text):
results = handler.handle(input_text)
breakapp.py
├── widget/window.py
│ ├── widget/search_entry.py
│ ├── widget/row.py
│ └── widget/footer.py
│
├── controller/search_controller.py
│ ├── handler/applications_handler.py
│ │ └── services/applications_service.py
│ │ └── models/applications_model.py
│ │
│ ├── handler/math_handler.py
│ │ └── services/math_service.py
│ │
│ └── helper/parser.py
│
├── services/extensions_service.py
│ ├── models/extension_model.py
│ └── helper/dynamic_loader.py
│
└── database/sqlite3.py
PyGObject (gi)
├── Gtk 4.0
├── Adw 1.0
├── Gio 2.0
└── GLib 2.0
google-generativeai
└── (for AI features)
Python Standard Library
├── sqlite3
├── pathlib
├── typing
├── dataclasses
├── importlib
└── logging
-
Desktop File Cache: Parsed
.desktopfiles cached in memory - Icon Cache: Resolved icon paths stored
- Database Connection Pool: Reuse database connections
- Class Instance Cache: Loaded classes cached to avoid reimport
- Main Thread: UI rendering and event handling
- Background Threads: Heavy operations (file scanning, database queries)
- Thread Pool: Configurable size for concurrent operations
- Weak references for cached objects
- Explicit cleanup in destructors
- Limited cache sizes with LRU eviction
- Limited filesystem access
- Network access controlled
- No direct system modification
- Portal-based file access
- Math expressions: Safe evaluation without
eval() - Commands: Optional whitelist
- SQL: Parameterized queries (no injection)
- File paths: Sanitized and validated
- Create handler class extending
BaseInputHandler - Create service class with business logic
- Define extension in
extensions.yaml - Extension automatically loaded and registered
Extensions can register custom services:
class CustomService:
def __init__(self):
pass
def custom_operation(self):
pass
# Register with extension service
extensions_service.register_service("custom", CustomService())- See API Reference for detailed API documentation
- Read Contributing for development guidelines
- Check Features for implemented functionality