189 lines
5.2 KiB
Python
189 lines
5.2 KiB
Python
"""
|
|
Hardware Abstraction Layer (HAL) interface for DReader.
|
|
|
|
This module defines the abstract interface that platform-specific
|
|
display/input implementations must provide.
|
|
|
|
The HAL separates the core e-reader logic from platform-specific
|
|
hardware details (display, touch input, buttons, etc.).
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import AsyncIterator, Optional
|
|
from PIL import Image
|
|
|
|
from .gesture import TouchEvent
|
|
|
|
|
|
class DisplayHAL(ABC):
|
|
"""
|
|
Abstract interface for display and input hardware.
|
|
|
|
Platform-specific implementations should subclass this and provide
|
|
concrete implementations for all abstract methods.
|
|
|
|
The HAL is responsible for:
|
|
- Displaying images on the screen
|
|
- Capturing touch/click input and converting to TouchEvent
|
|
- Hardware-specific features (brightness, sleep, etc.)
|
|
|
|
All methods are async to support non-blocking I/O.
|
|
"""
|
|
|
|
@abstractmethod
|
|
async def show_image(self, image: Image.Image):
|
|
"""
|
|
Display a PIL Image on the screen.
|
|
|
|
Args:
|
|
image: PIL Image to display
|
|
|
|
This method should handle:
|
|
- Converting image format if needed for the display
|
|
- Scaling/cropping if image size doesn't match display
|
|
- Updating the physical display hardware
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def get_touch_event(self) -> Optional[TouchEvent]:
|
|
"""
|
|
Wait for and return the next touch event.
|
|
|
|
Returns:
|
|
TouchEvent if available, None if no event (non-blocking mode)
|
|
|
|
This method should:
|
|
- Read from touch hardware
|
|
- Convert raw coordinates to TouchEvent
|
|
- Detect gesture type (tap, swipe, etc.)
|
|
- Return None immediately if no event available
|
|
|
|
Note: For blocking behavior, implement a loop that awaits this
|
|
method in the main event loop.
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def set_brightness(self, level: int):
|
|
"""
|
|
Set display brightness.
|
|
|
|
Args:
|
|
level: Brightness level (0-10, where 0=dimmest, 10=brightest)
|
|
|
|
Platform implementations should map this to their hardware's
|
|
actual brightness range.
|
|
"""
|
|
pass
|
|
|
|
async def initialize(self):
|
|
"""
|
|
Initialize the display hardware.
|
|
|
|
This optional method is called once before the application starts.
|
|
Override to perform platform-specific initialization.
|
|
"""
|
|
pass
|
|
|
|
async def cleanup(self):
|
|
"""
|
|
Clean up display hardware resources.
|
|
|
|
This optional method is called during application shutdown.
|
|
Override to perform platform-specific cleanup.
|
|
"""
|
|
pass
|
|
|
|
async def show_message(self, message: str, duration: float = 2.0):
|
|
"""
|
|
Display a text message (for loading screens, errors, etc.).
|
|
|
|
Args:
|
|
message: Text message to display
|
|
duration: How long to show message (seconds)
|
|
|
|
Default implementation creates a simple text image.
|
|
Override for platform-specific message display.
|
|
"""
|
|
from PIL import ImageDraw, ImageFont
|
|
|
|
# Create simple text image
|
|
img = Image.new('RGB', (800, 1200), color=(255, 255, 255))
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Try to use a decent font, fall back to default
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
# Draw centered text
|
|
bbox = draw.textbbox((0, 0), message, font=font)
|
|
text_width = bbox[2] - bbox[0]
|
|
text_height = bbox[3] - bbox[1]
|
|
x = (800 - text_width) // 2
|
|
y = (1200 - text_height) // 2
|
|
|
|
draw.text((x, y), message, fill=(0, 0, 0), font=font)
|
|
|
|
await self.show_image(img)
|
|
|
|
# Wait for duration
|
|
if duration > 0:
|
|
import asyncio
|
|
await asyncio.sleep(duration)
|
|
|
|
|
|
class KeyboardInputHAL(ABC):
|
|
"""
|
|
Optional abstract interface for keyboard input.
|
|
|
|
This is separate from DisplayHAL to support platforms that have
|
|
both touch and keyboard input (e.g., desktop testing).
|
|
"""
|
|
|
|
@abstractmethod
|
|
async def get_key_event(self) -> Optional[str]:
|
|
"""
|
|
Get the next keyboard event.
|
|
|
|
Returns:
|
|
Key name as string (e.g., "up", "down", "enter", "q")
|
|
None if no key event available
|
|
"""
|
|
pass
|
|
|
|
|
|
class EventLoopHAL(DisplayHAL):
|
|
"""
|
|
Extended HAL interface that provides its own event loop.
|
|
|
|
Some platforms (e.g., Pygame, Qt) have their own event loop that
|
|
must be used. This interface allows the HAL to run the main loop
|
|
and call back to the application.
|
|
|
|
Usage:
|
|
hal = MyEventLoopHAL()
|
|
app = DReaderApplication(AppConfig(display_hal=hal, ...))
|
|
|
|
await hal.run_event_loop(app)
|
|
"""
|
|
|
|
@abstractmethod
|
|
async def run_event_loop(self, app):
|
|
"""
|
|
Run the platform's event loop.
|
|
|
|
Args:
|
|
app: DReaderApplication instance to send events to
|
|
|
|
This method should:
|
|
1. Initialize the display
|
|
2. Call app.start()
|
|
3. Enter event loop
|
|
4. Call app.handle_touch(event) for each event
|
|
5. Handle quit events and call app.shutdown()
|
|
"""
|
|
pass
|