# 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