Skip to content

Architecture

BottyIvan edited this page Jun 15, 2026 · 2 revisions

Architecture

Understand the design, structure, and technical implementation of Launcher.

Table of Contents

Architecture Overview

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)     │
└─────────────────────────────────────────┘

Design Principles

1. Separation of Concerns

  • UI Layer: Only responsible for presentation
  • Controller Layer: Handles user interactions
  • Service Layer: Contains business logic
  • Data Layer: Manages persistence

2. Modularity

  • Independent, reusable components
  • Clear interfaces between modules
  • Pluggable architecture for extensions

3. Type Safety

  • Comprehensive type hints throughout
  • Static type checking with MyPy
  • Better IDE support and autocomplete

4. Performance First

  • Lazy loading of resources
  • Caching strategies at multiple levels
  • Background processing for heavy operations
  • Optimized database queries

5. Extensibility

  • Plugin/extension system
  • Service registration pattern
  • Dynamic class loading
  • YAML-based configuration

Project Structure

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

Core Components

1. Application Layer

App (app.py)

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.

Main Window (widget/window.py)

The primary UI container:

  • Creates and manages UI layout
  • Handles window events
  • Coordinates between widgets

2. Service Layer

ApplicationsService

Purpose: Manage application discovery and search

Responsibilities:

  • Scan desktop files from standard locations
  • Parse .desktop entries
  • 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) -> None

ExtensionsService

Purpose: 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]

MathService

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) -> str

3. Handler Layer

Base Input Handler

Abstract 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

Handler Implementations

  • ApplicationsHandler: Handles app search queries
  • MathHandler: Processes mathematical expressions
  • CommandHandler: Executes shell commands
  • AIHandler: Processes AI queries
  • LinkHandler: Manages web links

4. Controller Layer

SearchController

Manages search input and coordination:

  • Receives user input
  • Routes to appropriate handlers
  • Aggregates results
  • Updates UI

KeyController

Handles keyboard interactions:

  • Navigation (up/down arrows)
  • Selection (Enter)
  • Shortcuts (Ctrl+...)

ClickController

Manages mouse interactions:

  • Click events
  • Hover effects
  • Context menus

5. Data Layer

Database (database/sqlite3.py)

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."""
        pass

Models

Data 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: str

Data Flow

Search Flow

User 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)

Launch Flow

User Selection (click or Enter)
    ↓
ClickController / KeyController
    ↓
Application Model
    ↓
ApplicationsService.launch_application()
    ↓
Gio.AppInfo.launch()
    ↓
Application Started

Extension Loading Flow

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

Technology Stack

Core Technologies

Python 3.11+

  • Modern Python features
  • Type hints and annotations
  • Async/await support

GTK4

  • Modern UI toolkit
  • Hardware-accelerated rendering
  • Native Linux integration
  • Wayland and X11 support

Adwaita

  • GNOME design language
  • Adaptive layouts
  • Dark mode support
  • Modern widgets

PyGObject

  • Python bindings for GTK
  • GObject introspection
  • Full GTK4 access

Data & Storage

SQLite3

  • Embedded database
  • User preferences storage
  • Application cache
  • Extension state

YAML

  • Configuration files
  • Extension definitions
  • Resource manifests

Build & Distribution

Meson

  • Build system
  • Cross-platform support
  • Fast incremental builds

Flatpak

  • Sandboxed distribution
  • Dependency management
  • Cross-distro compatibility

Development Tools

Black

  • Code formatting
  • Consistent style
  • Line length: 100

Flake8

  • Code linting
  • Style checking
  • Error detection

MyPy

  • Static type checking
  • Type hint validation

Unittest

  • Unit testing framework
  • Test discovery
  • Assertions and mocking

Design Patterns

1. Service Pattern

Services encapsulate business logic:

class ApplicationsService:
    def __init__(self):
        self._cache = {}
    
    def search(self, query: str) -> List[Application]:
        # Business logic here
        pass

2. Handler Pattern

Handlers 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))]

3. Observer Pattern

GTK signals for event handling:

search_entry.connect("changed", self._on_search_changed)

4. Factory Pattern

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()

5. Singleton Pattern

Single database connection:

class Database:
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

6. Strategy Pattern

Pluggable handlers for different input types:

handlers = [
    ApplicationsHandler(),
    MathHandler(),
    CommandHandler(),
]

for handler in handlers:
    if handler.can_handle(input_text):
        results = handler.handle(input_text)
        break

Module Dependencies

Dependency Graph

app.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

External Dependencies

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

Performance Considerations

Caching Strategy

  1. Desktop File Cache: Parsed .desktop files cached in memory
  2. Icon Cache: Resolved icon paths stored
  3. Database Connection Pool: Reuse database connections
  4. Class Instance Cache: Loaded classes cached to avoid reimport

Threading Model

  • Main Thread: UI rendering and event handling
  • Background Threads: Heavy operations (file scanning, database queries)
  • Thread Pool: Configurable size for concurrent operations

Memory Management

  • Weak references for cached objects
  • Explicit cleanup in destructors
  • Limited cache sizes with LRU eviction

Security Architecture

Sandboxing (Flatpak)

  • Limited filesystem access
  • Network access controlled
  • No direct system modification
  • Portal-based file access

Input Validation

  • Math expressions: Safe evaluation without eval()
  • Commands: Optional whitelist
  • SQL: Parameterized queries (no injection)
  • File paths: Sanitized and validated

Extensibility

Adding New Extensions

  1. Create handler class extending BaseInputHandler
  2. Create service class with business logic
  3. Define extension in extensions.yaml
  4. Extension automatically loaded and registered

Custom Services

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())

Next Steps

Clone this wiki locally