Updated Braille support now that I'm more familiar with how it should work.

This commit is contained in:
Storm Dragon 2024-12-08 04:37:53 -05:00
parent 84514edc96
commit 1696d62526
2 changed files with 249 additions and 73 deletions

View File

@ -2,65 +2,167 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
from fenrirscreenreader.core.brailleDriver import brailleDriver from fenrirscreenreader.core.brailleDriver import brailleDriver
class driver(brailleDriver): class driver(brailleDriver):
"""BRLAPI implementation of the braille driver interface.
Supports all braille displays compatible with BRLTTY."""
def __init__(self): def __init__(self):
"""Initialize the BRLAPI driver."""
brailleDriver.__init__(self) brailleDriver.__init__(self)
self._brl = None self._brl = None
self._last_written_text = ""
def initialize(self, environment): def initialize(self, environment):
"""Initialize the braille driver with BRLAPI connection."""
self.env = environment self.env = environment
try: try:
import brlapi import brlapi
self._brl = brlapi.Connection() self._brl = brlapi.Connection()
self._deviceSize = self._brl.displaySize self._deviceSize = self._brl.displaySize
# Accept all key types from any braille display
self._brl.acceptKeys(brlapi.rangeType_all, [0, brlapi.KEY_MAX])
self._current_cursor_pos = 0
except Exception as e: except Exception as e:
print(e)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
return return
self._isInitialized = True self._isInitialized = True
def getDeviceSize(self): def getDeviceSize(self):
if not self._isInitialized: """Get the size of the connected braille display."""
return (0,0) if not self._isInitialized or not self._deviceSize:
if not self._deviceSize:
return (0, 0) return (0, 0)
return self._deviceSize return self._deviceSize
def flush(self):
if not self._isInitialized:
return
try:
self._brl.writeText('',0)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.flush '+str(e),debug.debugLevel.ERROR)
def writeText(self, text): def writeText(self, text):
"""Write text to the braille display."""
if not self._isInitialized: if not self._isInitialized:
return return
try: try:
self._last_written_text = text
# Handle unicode properly for international braille
if isinstance(text, str):
self._brl.writeText(text) self._brl.writeText(text)
else:
self._brl.writeText(text.decode('utf-8', 'replace'))
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.writeText ' + str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('BRAILLE.writeText ' + str(e), debug.debugLevel.ERROR)
def connectDevice(self): def connectDevice(self):
"""Establish connection with BRLAPI."""
try:
import brlapi
self._brl = brlapi.Connection() self._brl = brlapi.Connection()
# Accept all key types from the connected display
self._brl.acceptKeys(brlapi.rangeType_all, [0, brlapi.KEY_MAX])
return True
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.connectDevice ' + str(e), debug.debugLevel.ERROR)
return False
def enterScreen(self, screen): def enterScreen(self, screen):
"""Enter a specific TTY mode."""
if not self._isInitialized: if not self._isInitialized:
return return
try:
self._brl.enterTtyMode(int(screen)) self._brl.enterTtyMode(int(screen))
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.enterScreen ' + str(e), debug.debugLevel.ERROR)
def leveScreen(self): def leaveScreen(self):
"""Leave the current TTY mode."""
if not self._isInitialized: if not self._isInitialized:
return return
try:
self._brl.leaveTtyMode() self._brl.leaveTtyMode()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.leaveScreen ' + str(e), debug.debugLevel.ERROR)
def setCursor(self, position):
"""Set the cursor position on the braille display."""
if not self._isInitialized:
return
try:
max_pos = self.getDeviceSize()[0] - 1
self._current_cursor_pos = max(0, min(position, max_pos))
# Use BRLAPI's cursor command which works across different displays
self._brl.writeDots(1 << self._current_cursor_pos)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.setCursor ' + str(e), debug.debugLevel.ERROR)
def getCursorPosition(self):
"""Get the current cursor position."""
if not self._isInitialized:
return 0
return self._current_cursor_pos
def clear(self):
"""Clear the braille display."""
if not self._isInitialized:
return
try:
self._brl.writeText('')
self._last_written_text = ""
self._current_cursor_pos = 0
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.clear ' + str(e), debug.debugLevel.ERROR)
def flush(self):
"""Force an update of the physical display."""
if not self._isInitialized:
return
try:
if self._last_written_text:
self._brl.writeText(self._last_written_text)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.flush ' + str(e), debug.debugLevel.ERROR)
def handleKeyInput(self, key):
"""Handle input from any BRLTTY-compatible braille display.
Returns the key event for processing by Fenrir's input system."""
if not self._isInitialized:
return False
try:
key_code = self._brl.readKey(wait=0)
if not key_code:
return False
# Let Fenrir's input system handle the key
# This allows proper handling regardless of display type
if self.env['runtime']['debug'].debugLevel >= debug.debugLevel.INFO:
self.env['runtime']['debug'].writeDebugOut(
'BRAILLE.keyPressed: ' + str(key_code),
debug.debugLevel.INFO
)
return True
except brlapi.ConnectionError:
self.env['runtime']['debug'].writeDebugOut(
'BRAILLE: Connection lost. Attempting to reconnect...',
debug.debugLevel.ERROR
)
self.connectDevice()
return False
except Exception as e:
self.env['runtime']['debug'].writeDebugOut(
'BRAILLE.handleKeyInput ' + str(e),
debug.debugLevel.ERROR
)
return False
def shutdown(self): def shutdown(self):
"""Shut down the BRLAPI driver."""
if not self._isInitialized: if not self._isInitialized:
return return
self.leveScreen() try:
self.clear()
self.leaveScreen()
if self._brl:
self._brl.closeConnection()
self._brl = None
self._isInitialized = False
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.shutdown ' + str(e), debug.debugLevel.ERROR)

View File

@ -1,82 +1,156 @@
#!/usr/bin/python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors. # By Chrys, Storm Dragon, and contributors.
import brlapi from abc import ABC, abstractmethod
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
class brailleDriver(): class brailleDriver(ABC):
"""Base class for Fenrir braille display drivers.
This abstract base class defines the interface that all braille drivers must implement.
It provides basic initialization state tracking and defines the required methods
for interacting with braille displays.
"""
def __init__(self): def __init__(self):
"""Initialize the driver with default state."""
self._isInitialized = False self._isInitialized = False
self._brl = None
self.deviceSize = None self.deviceSize = None
self.env = None
self._current_cursor_pos = 0
self._display_content = ""
@abstractmethod
def initialize(self, environment): def initialize(self, environment):
"""Initialize the BRLTTY connection.""" """Initialize the braille driver with the given environment.
self.env = environment
try:
self._brl = brlapi.Connection()
self._brl.enterTtyMode()
self.deviceSize = self._brl.displaySize
self._isInitialized = True
return True
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Initializing braille failed:' + str(e),debug.debugLevel.ERROR)
return False
Args:
environment (dict): The Fenrir environment dictionary containing runtime settings
and helper objects.
"""
self.env = environment
self._isInitialized = True
@abstractmethod
def getDeviceSize(self): def getDeviceSize(self):
"""Get the size of the braille display.""" """Get the size of the connected braille display.
Returns:
tuple: A (columns, rows) tuple indicating the display dimensions.
Returns (0, 0) if no display is connected or not initialized.
"""
if not self._isInitialized: if not self._isInitialized:
return (0, 0) return (0, 0)
return self.deviceSize if self.deviceSize else (0, 0) return (0, 0)
@abstractmethod
def writeText(self, text): def writeText(self, text):
"""Write text to the braille display.""" """Write text to the braille display.
Args:
text (str): The text to be written to the display.
"""
if not self._isInitialized: if not self._isInitialized:
return return
try:
self._brl.writeText(text)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Writing braille failed:' + str(e),debug.debugLevel.ERROR)
def readKeypress(self): @abstractmethod
"""Read a keypress from the braille display.""" def connectDevice(self):
if not self._isInitialized: """Establish connection with the braille display.
return None
try:
return self._brl.readKey(wait=0)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Reading key failed:' + str(e),debug.debugLevel.ERROR)
return None
This method should handle the initial connection setup with the display.
It should be called before any other display operations.
"""
pass
@abstractmethod
def enterScreen(self, screen): def enterScreen(self, screen):
"""Enter a new screen context.""" """Enter a specific screen/TTY mode.
if not self._isInitialized:
return
try:
self._brl.enterTtyModeWithPath(screen)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Entering screen failed:' + str(e),debug.debugLevel.ERROR)
def leaveScreen(self): Args:
"""Leave the current screen context.""" screen (int): The screen/TTY number to enter.
"""
if not self._isInitialized:
return
@abstractmethod
def leaveScreen(self):
"""Leave the current screen/TTY mode.
This method should clean up any screen-specific state and
prepare for potential screen switches.
"""
if not self._isInitialized:
return
@abstractmethod
def setCursor(self, position):
"""Set the cursor position on the braille display.
Args:
position (int): The position where the cursor should be placed.
"""
if not self._isInitialized:
return
self._current_cursor_pos = max(0, min(position, self.getDeviceSize()[0] - 1))
@abstractmethod
def getCursorPosition(self):
"""Get the current cursor position on the braille display.
Returns:
int: The current cursor position.
"""
if not self._isInitialized:
return 0
return self._current_cursor_pos
@abstractmethod
def clear(self):
"""Clear the braille display."""
if not self._isInitialized:
return
self._display_content = ""
self._current_cursor_pos = 0
@abstractmethod
def flush(self):
"""Force an update of the physical display with current content."""
if not self._isInitialized: if not self._isInitialized:
return return
try:
self._brl.leaveTtyMode()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Leaving screen failed:' + str(e),debug.debugLevel.ERROR)
def shutdown(self): def shutdown(self):
"""Shutdown the braille driver.""" """Shut down the braille driver and clean up resources.
This method ensures proper cleanup of resources and
disconnection from the braille display.
"""
if not self._isInitialized: if not self._isInitialized:
return return
try: self.clear()
self.leaveScreen() self.leaveScreen()
if self._brl:
self._brl.closeConnection()
self._brl = None
self._isInitialized = False self._isInitialized = False
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Shutting down braille failed:' + str(e),debug.debugLevel.ERROR) @property
def is_initialized(self):
"""Check if the driver is initialized.
Returns:
bool: True if the driver is initialized, False otherwise.
"""
return self._isInitialized
@abstractmethod
def handleKeyInput(self, key):
"""Handle input from the braille display's keys.
Args:
key: The key event from the braille display.
Returns:
bool: True if the key was handled, False otherwise.
"""
if not self._isInitialized:
return False
return False