Updated Braille support now that I'm more familiar with how it should work.
This commit is contained in:
		| @@ -2,65 +2,167 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| # Fenrir TTY screen reader | ||||
| # By Chrys, Storm Dragon, and contributers. | ||||
| # By Chrys, Storm Dragon, and contributors. | ||||
|  | ||||
| from fenrirscreenreader.core import debug | ||||
| from fenrirscreenreader.core.brailleDriver import brailleDriver | ||||
|  | ||||
| class driver(brailleDriver): | ||||
|     """BRLAPI implementation of the braille driver interface. | ||||
|     Supports all braille displays compatible with BRLTTY.""" | ||||
|      | ||||
|     def __init__(self): | ||||
|         """Initialize the BRLAPI driver.""" | ||||
|         brailleDriver.__init__(self) | ||||
|         self._brl = None | ||||
|         self._last_written_text = "" | ||||
|  | ||||
|     def initialize(self, environment): | ||||
|         """Initialize the braille driver with BRLAPI connection.""" | ||||
|         self.env = environment | ||||
|         try: | ||||
|             import brlapi | ||||
|             self._brl = brlapi.Connection() | ||||
|             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: | ||||
|             print(e) | ||||
|             self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR) | ||||
|             self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) | ||||
|             return | ||||
|         self._isInitialized = True | ||||
|  | ||||
|     def getDeviceSize(self): | ||||
|         if not self._isInitialized: | ||||
|             return (0,0) | ||||
|         if not self._deviceSize: | ||||
|             return (0,0) | ||||
|         """Get the size of the connected braille display.""" | ||||
|         if not self._isInitialized or not self._deviceSize: | ||||
|             return (0, 0) | ||||
|         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: | ||||
|             return | ||||
|         try: | ||||
|             self._last_written_text = text | ||||
|             # Handle unicode properly for international braille | ||||
|             if isinstance(text, str): | ||||
|                 self._brl.writeText(text) | ||||
|             else: | ||||
|                 self._brl.writeText(text.decode('utf-8', 'replace')) | ||||
|         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): | ||||
|         """Establish connection with BRLAPI.""" | ||||
|         try: | ||||
|             import brlapi | ||||
|             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): | ||||
|         """Enter a specific TTY mode.""" | ||||
|         if not self._isInitialized: | ||||
|             return | ||||
|         try: | ||||
|             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: | ||||
|             return | ||||
|         try: | ||||
|             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): | ||||
|         """Shut down the BRLAPI driver.""" | ||||
|         if not self._isInitialized: | ||||
|             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) | ||||
|   | ||||
| @@ -1,82 +1,156 @@ | ||||
| #!/usr/bin/python | ||||
| #!/usr/bin/env python3 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| # Fenrir TTY screen reader | ||||
| # By Chrys, Storm Dragon, and contributors. | ||||
|  | ||||
| import brlapi | ||||
| from abc import ABC, abstractmethod | ||||
| 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): | ||||
|         """Initialize the driver with default state.""" | ||||
|         self._isInitialized = False | ||||
|         self._brl = None | ||||
|         self.deviceSize = None | ||||
|         self.env = None | ||||
|         self._current_cursor_pos = 0 | ||||
|         self._display_content = "" | ||||
|      | ||||
|     @abstractmethod | ||||
|     def initialize(self, environment): | ||||
|         """Initialize the BRLTTY connection.""" | ||||
|         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 | ||||
|         """Initialize the braille driver with the given environment. | ||||
|          | ||||
|         Args: | ||||
|             environment (dict): The Fenrir environment dictionary containing runtime settings | ||||
|                               and helper objects. | ||||
|         """ | ||||
|         self.env = environment | ||||
|         self._isInitialized = True | ||||
|  | ||||
|     @abstractmethod | ||||
|     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: | ||||
|             return (0, 0) | ||||
|         return self.deviceSize if self.deviceSize else (0, 0) | ||||
|         return (0, 0) | ||||
|  | ||||
|     @abstractmethod | ||||
|     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: | ||||
|             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): | ||||
|         """Read a keypress from the braille display.""" | ||||
|         if not self._isInitialized: | ||||
|             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 | ||||
|     @abstractmethod | ||||
|     def connectDevice(self): | ||||
|         """Establish connection with the braille display. | ||||
|          | ||||
|         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): | ||||
|         """Enter a new screen context.""" | ||||
|         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) | ||||
|         """Enter a specific screen/TTY mode. | ||||
|          | ||||
|     def leaveScreen(self): | ||||
|         """Leave the current screen context.""" | ||||
|         Args: | ||||
|             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: | ||||
|             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): | ||||
|         """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: | ||||
|             return | ||||
|         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('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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user