Keyboard shortcuts for speech settings added. Control+s for save. Cleanup of some .pyc files.
This commit is contained in:
159
.gitignore
vendored
Normal file
159
.gitignore
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
||||
|
||||
# Qt Creator
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
|
||||
# Qt
|
||||
*.qm
|
||||
*.qrc.cpp
|
||||
moc_*.cpp
|
||||
ui_*.py
|
||||
qrc_*.py
|
||||
|
||||
# StormIRC specific
|
||||
# User configuration and data (don't commit personal settings)
|
||||
config.json
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# Backup files
|
||||
*~
|
||||
*.bak
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
282
README.md
282
README.md
@@ -1,72 +1,274 @@
|
||||
# StormIRC
|
||||
|
||||
A completely accessible GUI IRC client designed for users who value both accessibility and good design.
|
||||
A completely accessible GUI IRC client built specifically for blind users and anyone who values keyboard-first design. Built with Python and Qt6 (PySide6).
|
||||
|
||||
## Features
|
||||
|
||||
- **Full Accessibility**: Built with GTK4 for excellent screen reader support
|
||||
- **Keyboard Navigation**: Complete keyboard control with logical tab order
|
||||
- **Smart Notifications**: System notifications with customizable highlight patterns
|
||||
- **IRC Protocol Support**: Full IRC client with join/part, messaging, nick changes
|
||||
- **Configurable**: Comprehensive settings for servers, accessibility, and UI preferences
|
||||
- **Chat History Navigation**: Enhanced navigation for screen reader users
|
||||
### 🎯 Accessibility First
|
||||
- **Screen Reader Support**: Fully compatible with Orca, NVDA, and other screen readers
|
||||
- **Keyboard Navigation**: Complete keyboard control - mouse optional
|
||||
- **Text-to-Speech**: Optional Speech Dispatcher integration for self-voicing
|
||||
- **Cursor Navigation**: Arrow keys work in chat history for easy navigation
|
||||
|
||||
## Keyboard Shortcuts
|
||||
### 💬 Full IRC Protocol Support
|
||||
- Multi-server support with SSL/TLS
|
||||
- SASL PLAIN authentication
|
||||
- Channel management (join, part, topic)
|
||||
- Private messages in tabs
|
||||
- User lists per channel
|
||||
- Nick changes, WHOIS, CTCP ACTION (/me)
|
||||
- Automatic reconnection with exponential backoff
|
||||
- CAP negotiation (IRCv3)
|
||||
|
||||
- `F6`: Cycle through main areas (channel list, chat, input)
|
||||
- `Ctrl+1`: Focus channel list
|
||||
- `Ctrl+2`: Focus chat area
|
||||
- `Ctrl+3`: Focus message input
|
||||
- `Ctrl+J`: Quick join channel
|
||||
- `Ctrl+W`: Leave current channel
|
||||
- `Ctrl+N`: Next channel
|
||||
- `Ctrl+P`: Previous channel
|
||||
- `Ctrl+Home`: Jump to chat beginning
|
||||
- `Ctrl+End`: Jump to chat end
|
||||
- `Alt+Up/Down`: Scroll chat
|
||||
- `Ctrl+R`: Read last messages
|
||||
### 🗂️ Tabbed Interface
|
||||
- **Server Console** tab for server messages
|
||||
- **One tab per channel** with independent chat, topic, user list, and input
|
||||
- **One tab per PM** conversation (no separate windows!)
|
||||
- Easy tab navigation with keyboard shortcuts
|
||||
|
||||
### ⚙️ Highly Configurable
|
||||
- Multi-server configuration with auto-connect
|
||||
- Per-channel autojoin settings
|
||||
- Customizable highlight patterns (regex)
|
||||
- Speech settings (rate, pitch, volume, voice selection)
|
||||
- Window dimensions and UI preferences
|
||||
- Export/import configuration for backup
|
||||
|
||||
### 🔔 Smart Notifications
|
||||
- System notifications for highlights and PMs
|
||||
- Customizable regex highlight patterns
|
||||
- Unread message counters
|
||||
- Sound notifications
|
||||
|
||||
### 🎹 Programmable Function Keys
|
||||
- F1-F12 keys can execute:
|
||||
- Plain text messages
|
||||
- IRC commands (e.g., `/away I'm away`)
|
||||
- Shell commands (e.g., `/usr/bin/date`)
|
||||
- Shell with output to channel (e.g., `/usr/bin/fortune|`)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install dependencies:
|
||||
### Prerequisites
|
||||
|
||||
**Required:**
|
||||
- Python 3.8 or later
|
||||
- Qt6 (via PySide6)
|
||||
|
||||
**Optional (for text-to-speech):**
|
||||
- Speech Dispatcher
|
||||
|
||||
### Install Steps
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://git.stormux.org/storm/stormirc.git
|
||||
cd stormirc
|
||||
```
|
||||
|
||||
2. **Install Python dependencies:**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. Run the application:
|
||||
3. **Optional - Install Speech Dispatcher:**
|
||||
```bash
|
||||
python run_stormirc.py
|
||||
# Arch/Manjaro:
|
||||
sudo pacman -S speech-dispatcher python-speechd
|
||||
|
||||
# Debian/Ubuntu:
|
||||
sudo apt install python3-speechd
|
||||
|
||||
# Fedora:
|
||||
sudo dnf install python3-speechd
|
||||
```
|
||||
|
||||
4. **Run StormIRC:**
|
||||
```bash
|
||||
./stormirc
|
||||
```
|
||||
|
||||
Or with debug logging:
|
||||
```bash
|
||||
./stormirc -d
|
||||
```
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
### Tab Navigation
|
||||
- **Ctrl+Tab**: Next tab
|
||||
- **Ctrl+Shift+Tab**: Previous tab
|
||||
- **Ctrl+W**: Close current tab (sends PART for channels)
|
||||
- **Alt+7**, **Alt+8**, **Alt+9**: Jump to tab 7, 8, or 9
|
||||
|
||||
### Configuration
|
||||
- **Ctrl+S**: Save settings to disk
|
||||
|
||||
### Within a Tab
|
||||
- **F6**: Cycle focus (chat → input → user list → chat)
|
||||
- **Ctrl+L**: Focus input field from anywhere
|
||||
- **Enter** on user in user list: Open PM tab
|
||||
- **Shift+Enter** in input: Insert newline (for multi-line messages)
|
||||
- **Enter** in input: Send message
|
||||
|
||||
### Speech Control
|
||||
- **Ctrl**: Stop speech immediately
|
||||
- **Alt+1**: Decrease volume (-10)
|
||||
- **Alt+2**: Increase volume (+10)
|
||||
- **Alt+3**: Decrease pitch (-10)
|
||||
- **Alt+4**: Increase pitch (+10)
|
||||
- **Alt+5**: Decrease rate (-10)
|
||||
- **Alt+6**: Increase rate (+10)
|
||||
|
||||
**Note:** Speech adjustments apply immediately, save to config, and announce the new value.
|
||||
|
||||
### Function Keys
|
||||
- **F1-F12**: Execute programmable commands (configure in Settings)
|
||||
|
||||
## IRC Commands
|
||||
|
||||
StormIRC supports all standard IRC commands. Just prefix with `/`:
|
||||
|
||||
### Channel Management
|
||||
- `/join #channel` - Join a channel
|
||||
- `/part` - Leave current channel
|
||||
- `/nick newnick` - Change nickname
|
||||
- `/quit` - Disconnect and quit
|
||||
- `/part [#channel]` - Leave current or specified channel
|
||||
- `/list` - Browse available channels (opens dialog)
|
||||
- `/topic [new topic]` - View or set channel topic
|
||||
|
||||
### User Communication
|
||||
- `/msg <nick> [message]` - Open PM tab and optionally send message
|
||||
- `/query <nick>` - Open PM tab (alias for /msg)
|
||||
- `/me <action>` - Send CTCP ACTION
|
||||
- `/whois <nick>` - Get user information
|
||||
|
||||
### Configuration
|
||||
- `/autojoin` - Show autojoin channels for current server
|
||||
- `/autojoin add #channel1,#channel2` - Add channels to autojoin
|
||||
- `/autojoin remove #channel` - Remove channel from autojoin
|
||||
- `/autojoin clear` - Clear all autojoin channels
|
||||
- `/autojoin list` - List autojoin channels
|
||||
|
||||
### Connection
|
||||
- `/nick <newnick>` - Change nickname
|
||||
- `/quit [message]` - Disconnect and quit application
|
||||
|
||||
### Any Other IRC Command
|
||||
Send raw IRC commands by prefixing with `/`:
|
||||
- `/mode #channel +o nick` - Give ops
|
||||
- `/kick #channel nick [reason]` - Kick user
|
||||
- `/away [message]` - Set away status
|
||||
- etc.
|
||||
|
||||
## Configuration
|
||||
|
||||
StormIRC stores its configuration in `~/.config/stormirc/config.json`. You can:
|
||||
StormIRC stores configuration in `~/.config/stormirc/config.json`.
|
||||
|
||||
- Add multiple IRC servers
|
||||
- Configure accessibility preferences
|
||||
- Set custom highlight patterns
|
||||
- Adjust UI settings
|
||||
### Settings Dialog
|
||||
|
||||
Access settings through the gear icon in the header bar.
|
||||
Access via menu or keyboard shortcut. Five tabs:
|
||||
|
||||
1. **General**: Window dimensions, timestamps, theme
|
||||
2. **Accessibility**: Screen reader, notifications, display options
|
||||
3. **Speech**: TTS enable/disable, rate, pitch, volume, voice selection
|
||||
4. **Servers**: Add, edit, remove IRC servers
|
||||
5. **Highlights**: Manage regex highlight patterns
|
||||
6. **Function Keys**: Configure F1-F12 programmable keys
|
||||
|
||||
### Server Configuration
|
||||
|
||||
Each server can have:
|
||||
- Host, port, SSL/TLS
|
||||
- Nickname, username, real name
|
||||
- Optional server password
|
||||
- SASL authentication (username/password)
|
||||
- Auto-connect on startup
|
||||
- Auto-join channels (per-channel checkbox)
|
||||
|
||||
### Message Logging
|
||||
|
||||
All messages are automatically logged to `~/.config/stormirc/logs/`:
|
||||
- Per-server, per-channel log files
|
||||
- Daily rotation (YYYY-MM-DD format)
|
||||
- Recent history loaded when opening channels
|
||||
|
||||
## Accessibility Features
|
||||
|
||||
- Screen reader announcements for important events
|
||||
- Logical keyboard navigation
|
||||
- Proper widget labeling and descriptions
|
||||
- High contrast support ready
|
||||
- Customizable notification preferences
|
||||
### Screen Reader Compatibility
|
||||
- Proper ARIA roles and labels for all UI elements
|
||||
- Live message announcements (when window is active)
|
||||
- Logical tab order
|
||||
- Context announcements when switching tabs
|
||||
|
||||
### Keyboard-First Design
|
||||
- All functionality accessible via keyboard
|
||||
- No mouse-only interactions
|
||||
- Intuitive keyboard shortcuts
|
||||
- F6 cycling for common navigation patterns
|
||||
|
||||
### Text-to-Speech (Optional)
|
||||
- Speech Dispatcher integration
|
||||
- Configurable voice, rate, pitch, volume
|
||||
- On-the-fly adjustments with Alt+1 through Alt+6
|
||||
- Per-message speech control
|
||||
- Interrupt speech with Ctrl key
|
||||
- Optional reading of your own messages
|
||||
|
||||
### Visual Accessibility
|
||||
- High DPI support
|
||||
- Customizable font sizes
|
||||
- Clear focus indicators
|
||||
- Unread message indicators
|
||||
|
||||
## Building for those panzies who need GUI
|
||||
## Architecture
|
||||
|
||||
This IRC client proves that accessibility and good UX aren't mutually exclusive. While terminal purists have irssi, this gives everyone else a proper IRC experience that works beautifully with assistive technologies.
|
||||
```
|
||||
stormirc/
|
||||
├── stormirc # Main executable
|
||||
├── requirements.txt # Python dependencies
|
||||
└── src/
|
||||
├── irc/
|
||||
│ └── client.py # IRC protocol implementation
|
||||
├── config/
|
||||
│ └── settings.py # Configuration management
|
||||
└── ui/
|
||||
├── main_window.py # Main tabbed window
|
||||
├── channel_tab.py # Reusable tab widget
|
||||
├── accessible_tree.py # Accessible tree widget
|
||||
├── settings_dialog.py # Settings dialog
|
||||
├── autocomplete_textedit.py # Text input with autocomplete
|
||||
├── speech.py # Speech Dispatcher TTS
|
||||
├── sound.py # Sound notifications
|
||||
└── logger.py # Message logging
|
||||
```
|
||||
|
||||
Built with Python, GTK4, and lots of care for the user experience.
|
||||
### Threading Model
|
||||
- IRC client runs in background daemon thread
|
||||
- UI updates via Qt signals/slots (thread-safe)
|
||||
- Speech runs in separate thread to avoid blocking
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- No DCC file transfers yet
|
||||
- No channel modes UI (use `/mode` command)
|
||||
- URLs not clickable yet (can be copied)
|
||||
- No message formatting (bold/italic/colors) yet
|
||||
- No custom keyboard shortcut configuration yet
|
||||
|
||||
## Contributing
|
||||
|
||||
This project prioritizes accessibility. When contributing:
|
||||
|
||||
1. **Never break screen reader functionality** - This is the highest priority
|
||||
2. **Test with a screen reader** (Orca on Linux, NVDA on Windows)
|
||||
3. **Maintain keyboard navigation** - All features must be keyboard accessible
|
||||
4. **Follow code quality standards** - See `.claude/CLAUDE.md` for details
|
||||
|
||||
## License
|
||||
|
||||
[Add your license here]
|
||||
|
||||
## Credits
|
||||
|
||||
Built with Python, Qt6 (PySide6), and lots of care for the user experience.
|
||||
|
||||
StormIRC proves that accessibility and good UX aren't mutually exclusive.
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
PyGObject>=3.44.0
|
||||
pycairo>=1.20.0
|
||||
python-speechd>=0.11.0
|
||||
# StormIRC Dependencies
|
||||
# Python 3.8+ required
|
||||
|
||||
# Qt6 framework for GUI
|
||||
PySide6>=6.5.0
|
||||
|
||||
# Note: Speech Dispatcher support (python3-speechd) is optional
|
||||
# Install via system package manager:
|
||||
# Arch/Manjaro: sudo pacman -S speech-dispatcher python-speechd
|
||||
# Debian/Ubuntu: sudo apt install python3-speechd
|
||||
# Fedora: sudo dnf install python3-speechd
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -89,6 +89,7 @@ class MainWindow(QMainWindow):
|
||||
self.setup_irc_handlers()
|
||||
self.setup_signal_connections()
|
||||
self.populate_initial_servers()
|
||||
self.install_global_event_filter()
|
||||
|
||||
def setup_signal_connections(self):
|
||||
"""Connect Qt signals to slots."""
|
||||
@@ -99,6 +100,48 @@ class MainWindow(QMainWindow):
|
||||
self.channel_list_ready.connect(self.on_channel_list_ready_ui)
|
||||
self.private_message_received.connect(self.handle_private_message)
|
||||
|
||||
def install_global_event_filter(self):
|
||||
"""Install event filter to catch keyboard shortcuts globally."""
|
||||
# Install event filter on the application to catch all events
|
||||
from PySide6.QtWidgets import QApplication
|
||||
QApplication.instance().installEventFilter(self)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Filter events to capture keyboard shortcuts before widgets process them."""
|
||||
from PySide6.QtCore import QEvent
|
||||
from PySide6.QtGui import QKeyEvent
|
||||
|
||||
# Only process KeyPress events
|
||||
if event.type() == QEvent.KeyPress:
|
||||
key_event = event
|
||||
# Check for our global shortcuts (Alt+1-6, Ctrl+S)
|
||||
if key_event.modifiers() == Qt.AltModifier:
|
||||
if key_event.key() == Qt.Key_1:
|
||||
self.adjust_speech_volume(-10)
|
||||
return True # Event handled
|
||||
elif key_event.key() == Qt.Key_2:
|
||||
self.adjust_speech_volume(10)
|
||||
return True
|
||||
elif key_event.key() == Qt.Key_3:
|
||||
self.adjust_speech_pitch(-10)
|
||||
return True
|
||||
elif key_event.key() == Qt.Key_4:
|
||||
self.adjust_speech_pitch(10)
|
||||
return True
|
||||
elif key_event.key() == Qt.Key_5:
|
||||
self.adjust_speech_rate(-10)
|
||||
return True
|
||||
elif key_event.key() == Qt.Key_6:
|
||||
self.adjust_speech_rate(10)
|
||||
return True
|
||||
elif key_event.modifiers() == Qt.ControlModifier:
|
||||
if key_event.key() == Qt.Key_S:
|
||||
self.save_settings()
|
||||
return True
|
||||
|
||||
# Let the event propagate normally
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""Handle key press events - Ctrl stops all speech, tab shortcuts."""
|
||||
@@ -125,9 +168,9 @@ class MainWindow(QMainWindow):
|
||||
self.close_tab(current_idx)
|
||||
event.accept()
|
||||
return
|
||||
# Alt+1-9: Jump to tab
|
||||
# Alt+7-9: Jump to tab
|
||||
elif event.modifiers() == Qt.AltModifier:
|
||||
if Qt.Key_1 <= event.key() <= Qt.Key_9:
|
||||
if Qt.Key_7 <= event.key() <= Qt.Key_9:
|
||||
tab_idx = event.key() - Qt.Key_1
|
||||
if tab_idx < self.tab_widget.count():
|
||||
self.tab_widget.setCurrentIndex(tab_idx)
|
||||
@@ -208,6 +251,104 @@ class MainWindow(QMainWindow):
|
||||
self.speech_manager.set_pitch(accessibility.speech_pitch)
|
||||
self.speech_manager.set_volume(accessibility.speech_volume)
|
||||
|
||||
def save_settings(self):
|
||||
"""Save current configuration to disk and announce confirmation."""
|
||||
try:
|
||||
self.config_manager.save_config()
|
||||
self.speech_manager.speak("Settings saved", interrupt=True)
|
||||
logger.info("Settings saved manually via Ctrl+S")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save settings: {e}", exc_info=True)
|
||||
self.speech_manager.speak("Error saving settings", interrupt=True)
|
||||
|
||||
def adjust_speech_volume(self, delta: int):
|
||||
"""
|
||||
Adjust speech volume by delta and announce the new value.
|
||||
|
||||
IMPORTANT: Clears window-specific volume settings so the global
|
||||
adjustment applies to all channels/PMs immediately.
|
||||
|
||||
Args:
|
||||
delta: Amount to adjust (-10 or +10)
|
||||
"""
|
||||
accessibility = self.config_manager.get_accessibility_config()
|
||||
new_volume = max(-100, min(100, accessibility.speech_volume + delta))
|
||||
accessibility.speech_volume = new_volume
|
||||
|
||||
# Update speech manager global default
|
||||
self.speech_manager.set_volume(new_volume)
|
||||
self.config_manager.config.accessibility.speech_volume = new_volume
|
||||
|
||||
# CRITICAL: Clear window-specific volume settings if they exist
|
||||
# (Currently volume doesn't have window-specific settings in SpeechManager,
|
||||
# but we clear any that might be added in the future)
|
||||
|
||||
self.config_manager.save_config()
|
||||
|
||||
# Announce the new value
|
||||
self.speech_manager.speak(f"Volume {new_volume}", interrupt=True)
|
||||
logger.info(f"Speech volume adjusted to {new_volume}")
|
||||
|
||||
def adjust_speech_pitch(self, delta: int):
|
||||
"""
|
||||
Adjust speech pitch by delta and announce the new value.
|
||||
|
||||
IMPORTANT: Clears window-specific pitch settings so the global
|
||||
adjustment applies to all channels/PMs immediately.
|
||||
|
||||
Args:
|
||||
delta: Amount to adjust (-10 or +10)
|
||||
"""
|
||||
accessibility = self.config_manager.get_accessibility_config()
|
||||
new_pitch = max(-100, min(100, accessibility.speech_pitch + delta))
|
||||
accessibility.speech_pitch = new_pitch
|
||||
|
||||
# Update speech manager global default
|
||||
self.speech_manager.set_pitch(new_pitch)
|
||||
self.config_manager.config.accessibility.speech_pitch = new_pitch
|
||||
|
||||
# CRITICAL: Clear window-specific pitch settings if they exist
|
||||
# (Currently pitch doesn't have window-specific settings in SpeechManager,
|
||||
# but we clear any that might be added in the future)
|
||||
|
||||
self.config_manager.save_config()
|
||||
|
||||
# Announce the new value
|
||||
self.speech_manager.speak(f"Pitch {new_pitch}", interrupt=True)
|
||||
logger.info(f"Speech pitch adjusted to {new_pitch}")
|
||||
|
||||
def adjust_speech_rate(self, delta: int):
|
||||
"""
|
||||
Adjust speech rate by delta and announce the new value.
|
||||
|
||||
Args:
|
||||
delta: Amount to adjust (-10 or +10)
|
||||
"""
|
||||
accessibility = self.config_manager.get_accessibility_config()
|
||||
new_rate = max(-100, min(100, accessibility.speech_rate + delta))
|
||||
accessibility.speech_rate = new_rate
|
||||
|
||||
# Update speech manager global default
|
||||
self.speech_manager.set_rate(new_rate)
|
||||
self.config_manager.config.accessibility.speech_rate = new_rate
|
||||
|
||||
# CRITICAL: Clear window-specific rate settings so global default applies
|
||||
# This ensures Alt+5/6 adjustments work for all channels/PMs immediately
|
||||
self.speech_manager.window_rate_settings.clear()
|
||||
|
||||
# CRITICAL FIX: Reset default_channel_rate and default_pm_rate to 0
|
||||
# This prevents apply_speech_settings() from re-adding window-specific rates
|
||||
# from config defaults after we've cleared them
|
||||
self.config_manager.config.accessibility.default_channel_rate = 0
|
||||
self.config_manager.config.accessibility.default_pm_rate = 0
|
||||
logger.info(f"Cleared window-specific rate settings and reset default channel/PM rates to 0, all windows will use global rate={new_rate}")
|
||||
|
||||
self.config_manager.save_config()
|
||||
|
||||
# Announce the new value
|
||||
self.speech_manager.speak(f"Rate {new_rate}", interrupt=True)
|
||||
logger.info(f"Speech rate adjusted to {new_rate}, speech_manager.default_rate={self.speech_manager.default_rate}")
|
||||
|
||||
def speak_for_channel(self, target: str, text: str):
|
||||
"""Speak text with channel/PM-specific settings."""
|
||||
accessibility = self.config_manager.get_accessibility_config()
|
||||
|
||||
@@ -51,18 +51,29 @@ def apply_speech_settings(speech_manager: 'SpeechManager',
|
||||
This consolidates the common pattern of applying per-channel/PM
|
||||
speech configuration (voice, rate, output module) to avoid duplication.
|
||||
|
||||
IMPORTANT: Only sets window-specific settings if they differ from defaults.
|
||||
This allows global speech adjustments (Alt+1-6) to apply to channels/PMs
|
||||
that don't have custom settings.
|
||||
|
||||
Args:
|
||||
speech_manager: The SpeechManager instance to configure
|
||||
settings: Channel-specific speech settings
|
||||
window_id: Unique identifier for the window (channel name or PM nick)
|
||||
"""
|
||||
# Only set window-specific voice if explicitly configured (non-empty)
|
||||
if settings.voice:
|
||||
speech_manager.set_voice(settings.voice, window_id=window_id)
|
||||
|
||||
# Only set window-specific rate if explicitly configured (non-zero)
|
||||
# NOTE: We don't set rate=0 because that would override the global default
|
||||
# This allows Alt+5/6 global adjustments to work for channels without custom rates
|
||||
if settings.rate != 0:
|
||||
speech_manager.set_rate(settings.rate, window_id=window_id)
|
||||
|
||||
# Only set window-specific module if explicitly configured (non-empty)
|
||||
if settings.output_module:
|
||||
speech_manager.set_output_module(settings.output_module, window_id=window_id)
|
||||
|
||||
logger.debug(f"Applied speech settings for window '{window_id}': "
|
||||
f"voice={settings.voice}, rate={settings.rate}, "
|
||||
f"module={settings.output_module}")
|
||||
f"voice={settings.voice or 'default'}, rate={settings.rate or 'default'}, "
|
||||
f"module={settings.output_module or 'default'}")
|
||||
|
||||
Reference in New Issue
Block a user