# Fenrir Development Guide This document provides information for developers who want to contribute to Fenrir or understand its architecture. ## Project Structure Fenrir follows a modular, driver-based architecture: ``` src/fenrirscreenreader/ ├── core/ # Core system modules │ ├── fenrirManager.py # Main application manager │ ├── screenManager.py # Screen handling │ ├── inputManager.py # Input handling │ ├── outputManager.py # Speech/sound output │ ├── commandManager.py # Command system │ └── settingsManager.py # Configuration management ├── commands/ # Command implementations │ ├── commands/ # User-invoked commands │ ├── onCursorChange/ # Cursor movement hooks │ ├── onScreenUpdate/ # Screen update hooks │ ├── onKeyInput/ # Key input hooks │ └── help/ # Tutorial system ├── drivers/ # Driver implementations │ ├── inputDriver/ # Input drivers (evdev, pty, atspi) │ ├── screenDriver/ # Screen drivers (vcsa, pty) │ ├── speechDriver/ # Speech drivers (speechd, generic) │ └── soundDriver/ # Sound drivers (generic, gstreamer) └── utils/ # Utility modules ``` ## Core Architecture ### Driver System Fenrir uses a pluggable driver architecture: 1. **Input Drivers**: Capture keyboard input - evdevDriver: Linux evdev (recommended) - ptyDriver: Terminal emulation - atspiDriver: AT-SPI for desktop 2. **Screen Drivers**: Read screen content - vcsaDriver: Linux VCSA devices - ptyDriver: Terminal emulation 3. **Speech Drivers**: Text-to-speech output - speechdDriver: Speech-dispatcher - genericDriver: Command-line TTS 4. **Sound Drivers**: Audio output - genericDriver: Sox-based - gstreamerDriver: GStreamer 5. **Remote Drivers**: Remote control interfaces - unixDriver: Unix socket control - tcpDriver: TCP socket control ### Command System Commands are Python modules that implement specific functionality: ```python class command(): def __init__(self): pass def initialize(self, environment): self.env = environment def shutdown(self): pass def getDescription(self): return _('Command description') def run(self): # Command implementation pass ``` ### Event Hooks Fenrir supports various event hooks: - **onCursorChange**: Triggered when cursor moves - **onScreenUpdate**: Triggered on screen content changes - **onKeyInput**: Triggered on key presses - **onByteInput**: Triggered on byte-level input - **onScreenChanged**: Triggered when switching screens ## Development Setup ### Requirements - Python 3.6+ - python3-evdev - python3-pyudev - speech-dispatcher - sox ### Getting Started ```bash # Clone repository git clone https://git.stormux.org/storm/fenrir.git cd fenrir # Install dependencies sudo pip3 install -r requirements.txt # Run from source cd src/ sudo ./fenrir -f -d ``` ### Testing ```bash # Run in debug mode sudo ./fenrir -f -d -p # Debug output goes to: # - Console (with -p flag) # - /var/log/fenrir.log ``` ## Creating Commands ### Basic Command Create a file in `src/fenrirscreenreader/commands/commands/`: ```python from fenrirscreenreader.core import debug class command(): def __init__(self): pass def initialize(self, environment): self.env = environment def shutdown(self): pass def getDescription(self): return _('My custom command') def run(self): # Get current text text = self.env['screen']['newContentText'] # Speak something self.env['runtime']['outputManager'].presentText('Hello World') # Play sound self.env['runtime']['outputManager'].playSoundIcon('Accept') ``` ### Key Bindings Add key bindings in keyboard layout files: `config/keyboard/desktop.conf` or `config/keyboard/laptop.conf` ```ini [KEY_CTRL]#[KEY_ALT]#[KEY_H]=my_command ``` ### Event Hooks Create event handlers in appropriate directories: ```python # onCursorChange/my_hook.py class command(): def __init__(self): pass def initialize(self, environment): self.env = environment def shutdown(self): pass def getDescription(self): return _('My cursor change handler') def run(self): if self.env['runtime']['cursorManager'].isCursorHorizontalMove(): # Handle horizontal cursor movement pass ``` ## Creating Drivers ### Driver Template ```python class driver(): def __init__(self): pass def initialize(self, environment): self.env = environment def shutdown(self): pass # Driver-specific methods... ``` ### Input Driver Implement these methods: - `getInputEvent()`: Return input events - `writeEventBuffer()`: Handle output events - `grabDevices()`: Take exclusive control - `releaseDevices()`: Release control ### Screen Driver Implement these methods: - `getCurrScreen()`: Get current screen content - `getSessionInformation()`: Get session info ### Speech Driver Implement these methods: - `speak()`: Speak text - `cancel()`: Stop speech - `setCallback()`: Set callback functions ### Remote Driver Implement these methods: - `initialize()`: Setup socket/connection - `watchDog()`: Listen for incoming commands - `shutdown()`: Clean up connections #### Remote Driver Example ```python class driver(remoteDriver): def initialize(self, environment): self.env = environment # Start watchdog thread self.env['runtime']['processManager'].addCustomEventThread( self.watchDog, multiprocess=True ) def watchDog(self, active, eventQueue): # Listen for connections and process commands while active.value: # Accept connections # Parse incoming data # Send to event queue eventQueue.put({ "Type": fenrirEventType.RemoteIncomming, "Data": command_text }) ``` ## Configuration ### Settings System Settings are hierarchical: 1. Command-line options (`-o`) 2. Configuration file 3. Hard-coded defaults ### Adding Settings 1. Add default value to `core/settingsData.py` 2. Access via `self.env['runtime']['settingsManager'].getSetting(section, key)` ## Debugging ### Debug Levels - 0: DEACTIVE - 1: ERROR - 2: WARNING - 3: INFO ### Debug Output ```python self.env['runtime']['debug'].writeDebugOut( 'Debug message', debug.debugLevel.INFO ) ``` ### Testing Commands ```bash # Test specific functionality sudo fenrir -f -d -o "general#debugLevel=3" # Test with custom config sudo fenrir -f -s /path/to/test.conf ``` ## Contributing ### Code Style - Follow PEP 8 - Use descriptive variable names - Add docstrings for complex functions - Handle exceptions gracefully ### Testing - Test with different drivers - Test keyboard layouts - Test on different terminals - Verify accessibility features ### Submitting Changes 1. Fork the repository 2. Create feature branch 3. Make changes with clear commit messages 4. Test thoroughly 5. Submit pull request ## API Reference ### Environment Structure The `environment` dict contains all runtime data: ```python environment = { 'runtime': { 'settingsManager': settingsManager, 'commandManager': commandManager, 'screenManager': screenManager, 'inputManager': inputManager, 'outputManager': outputManager, 'debug': debugManager, # ... other managers }, 'screen': { 'newContentText': '', 'oldContentText': '', 'newCursor': {'x': 0, 'y': 0}, 'oldCursor': {'x': 0, 'y': 0}, # ... screen data }, 'general': { 'prevCommand': '', 'currCommand': '', # ... general data } } ``` ### Common Operations #### Speaking Text ```python self.env['runtime']['outputManager'].presentText('Hello') ``` #### Playing Sounds ```python self.env['runtime']['outputManager'].playSoundIcon('Accept') ``` #### Getting Settings ```python rate = self.env['runtime']['settingsManager'].getSetting('speech', 'rate') ``` #### Cursor Information ```python x = self.env['screen']['newCursor']['x'] y = self.env['screen']['newCursor']['y'] ``` #### Screen Content ```python text = self.env['screen']['newContentText'] lines = text.split('\n') current_line = lines[self.env['screen']['newCursor']['y']] ``` ## Maintenance ### Release Process 1. Update version in `fenrirVersion.py` 2. Update changelog 3. Test on multiple systems 4. Tag release 5. Update documentation ### Compatibility - Maintain Python 3.6+ compatibility - Test on multiple Linux distributions - Ensure driver compatibility - Check dependencies ## Resources - **Repository**: https://git.stormux.org/storm/fenrir - **Wiki**: https://git.stormux.org/storm/fenrir/wiki - **Issues**: Use repository issue tracker - **Community**: IRC irc.stormux.org #stormux - **Email**: stormux+subscribe@groups.io