389 lines
9.1 KiB
Plaintext
389 lines
9.1 KiB
Plaintext
# 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 |