> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/rtr46/meikipop/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture overview

> Understanding meikipop's threading model, core components, and application structure

Meikipop is built around a multi-threaded architecture that enables real-time OCR and dictionary lookups without blocking the user interface. This page explains the core architectural components and how they work together.

## Threading model

Meikipop uses a multi-threaded design where each major component runs in its own daemon thread. All threads share a `SharedState` object that coordinates communication through thread-safe queues and events.

### SharedState class

The `SharedState` class is the central coordination point for all threads:

```python theme={null}
class SharedState:
    def __init__(self):
        self.running = True

        # events and queues
        self.screenshot_trigger_event = threading.Event()
        self.ocr_queue = LatestValueQueue()
        self.hit_scan_queue = LatestValueQueue()
        self.lookup_queue = LatestValueQueue()

        # screen lock - used by screen manager and popup
        self.screen_lock = threading.RLock()
```

Key elements:

* **`running`**: Global flag to signal all threads to terminate gracefully
* **`screenshot_trigger_event`**: Signals when a new screenshot should be captured
* **Queues**: Custom `LatestValueQueue` instances that only keep the most recent value
* **`screen_lock`**: Prevents the popup from being included in screenshots

### LatestValueQueue

A specialized queue that only stores the most recent value:

```python theme={null}
class LatestValueQueue():
    def __init__(self):
        self._value = None
        self._lock = threading.Lock()
        self._event = threading.Event()

    def put(self, item):
        with self._lock:
            self._value = item
            self._event.set()

    def get(self):
        self._event.wait()
        with self._lock:
            value = self._value
            self._event.clear()
            return value
```

This ensures that if processing is slow, only the latest data is processed, preventing queue buildup.

## Core components

Meikipop consists of six main threaded components that communicate through the shared state:

<Steps>
  ### InputLoop thread

  Monitors keyboard and mouse input to trigger OCR operations.

  **Responsibilities:**

  * Polls hotkey state (platform-specific implementations for Windows, Linux, macOS)
  * Tracks mouse position changes
  * Triggers screenshots via `screenshot_trigger_event`
  * Triggers hit scans via `hit_scan_queue`

  **Key code** (from `src/gui/input.py:153`):

  ```python theme={null}
  while self.shared_state.running:
      if not config.is_enabled:
          time.sleep(0.1)
          continue
          
      current_mouse_pos = self.mouse_controller.position
      hotkey_is_pressed = self.keyboard_controller.is_hotkey_pressed()

      # trigger screenshots + ocr in manual mode
      if hotkey_is_pressed and not hotkey_was_pressed and not config.auto_scan_mode:
          self.shared_state.screenshot_trigger_event.set()

      # trigger hit_scans + lookups
      if current_mouse_pos != last_mouse_pos:
          self.shared_state.hit_scan_queue.put((False, None))
  ```

  ### ScreenManager thread

  Captures screenshots of the configured screen region.

  **Responsibilities:**

  * Waits for `screenshot_trigger_event`
  * Captures screenshots using `mss` library
  * Implements throttling for auto-scan mode
  * Converts screenshots to PIL images
  * Puts images into `ocr_queue`

  **Key code** (from `src/screenshot/screenmanager.py:34`):

  ```python theme={null}
  while self.shared_state.running:
      self.shared_state.screenshot_trigger_event.wait()
      self.shared_state.screenshot_trigger_event.clear()
      
      with self.shared_state.screen_lock:
          screenshot = self.take_screenshot()
      
      img = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
      self.shared_state.ocr_queue.put(img)
  ```

  ### OcrProcessor thread

  Processes screenshots and extracts Japanese text using pluggable OCR providers.

  **Responsibilities:**

  * Dynamically discovers OCR providers from `src/ocr/providers/`
  * Loads configured provider (Google Lens V2 by default)
  * Processes images from `ocr_queue`
  * Puts OCR results (list of paragraphs) into `hit_scan_queue`

  **Key code** (from `src/ocr/ocr.py:31`):

  ```python theme={null}
  while self.shared_state.running:
      screenshot = self.shared_state.ocr_queue.get()
      
      start_time = time.perf_counter()
      ocr_result = self.ocr_backend.scan(screenshot)
      logger.info(
          f"{self.ocr_backend.NAME} found {len(ocr_result)} paragraphs in {(time.perf_counter() - start_time):.3f}s."
      )
      
      self.shared_state.hit_scan_queue.put((True, ocr_result))
  ```

  <Note>
    OCR providers are discovered dynamically by scanning `src/ocr/providers/` for subdirectories containing classes that inherit from `OcrProvider`.
  </Note>

  ### HitScanner thread

  Determines which Japanese text the user's mouse is hovering over.

  **Responsibilities:**

  * Receives OCR results from `hit_scan_queue`
  * Calculates normalized mouse position relative to scan region
  * Performs hit detection on OCR bounding boxes
  * Determines character offset within the hovered word
  * Puts lookup strings into `lookup_queue`

  **Key code** (from `src/ocr/hit_scan.py:20`):

  ```python theme={null}
  while self.shared_state.running:
      is_ocr_result_updated, new_ocr_result = self.shared_state.hit_scan_queue.get()
      
      if is_ocr_result_updated:
          self.last_ocr_result = new_ocr_result

      hit_scan_result = self.hit_scan(self.last_ocr_result) if self.last_ocr_result else None
      self.shared_state.lookup_queue.put(hit_scan_result)
  ```

  ### Lookup thread

  Performs dictionary lookups with deconjugation.

  **Responsibilities:**

  * Loads JMdict dictionary and deconjugation rules
  * Receives lookup strings from `lookup_queue`
  * Performs incremental lookups with deconjugation
  * Implements priority-based sorting and filtering
  * Caches results (500 most recent lookups)
  * Sends formatted entries to popup window

  **Key code** (from `src/dictionary/lookup.py:58`):

  ```python theme={null}
  while self.shared_state.running:
      hit_result = self.shared_state.lookup_queue.get()
      
      # skip lookup if hit_result didnt change
      if hit_result == self.last_hit_result:
          continue
      self.last_hit_result = hit_result

      lookup_result = self.lookup(self.last_hit_result) if self.last_hit_result else None
      self.popup_window.set_latest_data(lookup_result)
  ```

  ### Popup window (Qt timer-based)

  Displays dictionary entries near the mouse cursor.

  **Responsibilities:**

  * Runs in the main Qt thread (not a daemon thread)
  * Polls for new lookup data via QTimer (every 10ms)
  * Renders HTML-formatted dictionary entries
  * Positions itself intelligently near the cursor
  * Acquires `screen_lock` when visible to prevent self-capture

  **Key code** (from `src/gui/popup.py:160`):

  ```python theme={null}
  def process_latest_data_loop(self):
      latest_data = self.get_latest_data()
      if latest_data and latest_data != self._last_latest_data:
          full_html, new_size = self._calculate_content_and_size_char_count(latest_data)
          self.display_label.setText(full_html)
          self.setFixedSize(new_size)

      if self._latest_data and self.input_loop.is_virtual_hotkey_down():
          self.show_popup()
      else:
          self.hide_popup()
  ```
</Steps>

## TrayIcon (non-threaded)

The system tray icon provides user interaction:

**Features:**

* Settings dialog access
* OCR provider selection
* Scan mode toggle (manual/auto)
* Scan area selection (region/screens)
* Pause/resume functionality
* Quit application

**Implementation** (from `src/gui/tray.py:28`):

```python theme={null}
class TrayIcon(QSystemTrayIcon):
    def __init__(self, screen_manager, ocr_processor, popup_window, input_loop, lookup):
        # Initialize with icon and create context menu
        # Provides access to all major components for configuration
```

## Application lifecycle

The main application flow (from `src/main.py:44`):

<Steps>
  ### Initialize Qt application

  ```python theme={null}
  app = QApplication(sys.argv)
  app.setQuitOnLastWindowClosed(False)
  ```

  ### Create SharedState and components

  ```python theme={null}
  shared_state = SharedState()
  input_loop = InputLoop(shared_state)
  popup_window = Popup(shared_state, input_loop)
  screen_manager = ScreenManager(shared_state, input_loop)
  lookup = Lookup(shared_state, popup_window)
  ocr_processor = OcrProcessor(shared_state, screen_manager)
  hit_scanner = HitScanner(shared_state, input_loop, screen_manager)
  tray_icon = TrayIcon(screen_manager, ocr_processor, popup_window, input_loop, lookup)
  ```

  ### Start all threads

  ```python theme={null}
  for t in [lookup, hit_scanner, ocr_processor, screen_manager, input_loop]:
      t.start()
  ```

  ### Run Qt event loop

  ```python theme={null}
  exit_code = app.exec()
  ```

  ### Clean shutdown

  ```python theme={null}
  shared_state.running = False
  shared_state.screenshot_trigger_event.set()
  shared_state.ocr_queue.put(None)
  shared_state.hit_scan_queue.put((False, None))
  shared_state.lookup_queue.put(None)
  ```
</Steps>

## Data flow diagram

Here's how data flows through the system:

```
InputLoop → screenshot_trigger_event → ScreenManager
    ↓                                        ↓
    ↓                                    ocr_queue
    ↓                                        ↓
    ↓                                  OcrProcessor
    ↓                                        ↓
    ↓                                 hit_scan_queue
    ↓                                        ↓
    └──────→ hit_scan_queue ←──────── HitScanner
                   ↓
              lookup_queue
                   ↓
                Lookup
                   ↓
            (direct method call)
                   ↓
                 Popup
```

<Info>
  All threads are daemon threads, meaning they will automatically terminate when the main thread exits.
</Info>

## Thread safety

Meikipop uses several strategies to ensure thread safety:

* **LatestValueQueue**: Thread-safe queue implementation with internal locking
* **screen\_lock**: RLock prevents popup from being captured in screenshots
* **\_data\_lock**: Protects popup's latest data from concurrent access
* **Daemon threads**: All worker threads are marked as daemon to ensure clean shutdown
* **Atomic operations**: Config changes are atomic and use file-based persistence

<Warning>
  When modifying the architecture, be careful about race conditions between the popup visibility state and screenshot capture. The `screen_lock` mechanism is critical for preventing the popup from appearing in OCR results.
</Warning>
