Initial commit: Bifrost accessible fediverse client
- Full ActivityPub support for Pleroma, GoToSocial, and Mastodon - Screen reader optimized interface with PySide6 - Timeline switching with tabs and keyboard shortcuts (Ctrl+1-4) - Threaded conversation navigation with expand/collapse - Cross-platform desktop notifications via plyer - Customizable sound pack system with audio feedback - Complete keyboard navigation and accessibility features - XDG Base Directory compliant configuration - Multiple account support with OAuth authentication 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
145
src/config/settings.py
Normal file
145
src/config/settings.py
Normal file
@ -0,0 +1,145 @@
|
||||
"""
|
||||
Settings management with XDG Base Directory specification compliance
|
||||
"""
|
||||
|
||||
import os
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class SettingsManager:
|
||||
"""Manages application settings with XDG compliance"""
|
||||
|
||||
def __init__(self):
|
||||
self.config_dir = self._get_config_dir()
|
||||
self.data_dir = self._get_data_dir()
|
||||
self.cache_dir = self._get_cache_dir()
|
||||
|
||||
# Ensure directories exist
|
||||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.config_file = self.config_dir / "bifrost.conf"
|
||||
self.config = configparser.ConfigParser()
|
||||
self.load_settings()
|
||||
|
||||
def _get_config_dir(self) -> Path:
|
||||
"""Get XDG config directory"""
|
||||
xdg_config = os.getenv('XDG_CONFIG_HOME')
|
||||
if xdg_config:
|
||||
return Path(xdg_config) / "bifrost"
|
||||
return Path.home() / ".config" / "bifrost"
|
||||
|
||||
def _get_data_dir(self) -> Path:
|
||||
"""Get XDG data directory"""
|
||||
xdg_data = os.getenv('XDG_DATA_HOME')
|
||||
if xdg_data:
|
||||
return Path(xdg_data) / "bifrost"
|
||||
return Path.home() / ".local" / "share" / "bifrost"
|
||||
|
||||
def _get_cache_dir(self) -> Path:
|
||||
"""Get XDG cache directory"""
|
||||
xdg_cache = os.getenv('XDG_CACHE_HOME')
|
||||
if xdg_cache:
|
||||
return Path(xdg_cache) / "bifrost"
|
||||
return Path.home() / ".cache" / "bifrost"
|
||||
|
||||
def load_settings(self):
|
||||
"""Load settings from config file"""
|
||||
if self.config_file.exists():
|
||||
self.config.read(self.config_file)
|
||||
else:
|
||||
self.create_default_config()
|
||||
|
||||
def create_default_config(self):
|
||||
"""Create default configuration"""
|
||||
# General settings
|
||||
self.config.add_section('general')
|
||||
self.config.set('general', 'instance_url', '')
|
||||
self.config.set('general', 'username', '')
|
||||
self.config.set('general', 'current_sound_pack', 'default')
|
||||
self.config.set('general', 'timeline_refresh_interval', '60')
|
||||
self.config.set('general', 'auto_refresh_enabled', 'true')
|
||||
|
||||
# Sound settings
|
||||
self.config.add_section('sounds')
|
||||
self.config.set('sounds', 'enabled', 'true')
|
||||
self.config.set('sounds', 'private_message_enabled', 'true')
|
||||
self.config.set('sounds', 'private_message_volume', '0.8')
|
||||
self.config.set('sounds', 'mention_enabled', 'true')
|
||||
self.config.set('sounds', 'mention_volume', '1.0')
|
||||
self.config.set('sounds', 'boost_enabled', 'true')
|
||||
self.config.set('sounds', 'boost_volume', '0.7')
|
||||
self.config.set('sounds', 'reply_enabled', 'true')
|
||||
self.config.set('sounds', 'reply_volume', '0.8')
|
||||
self.config.set('sounds', 'post_sent_enabled', 'true')
|
||||
self.config.set('sounds', 'post_sent_volume', '0.9')
|
||||
self.config.set('sounds', 'timeline_update_enabled', 'true')
|
||||
self.config.set('sounds', 'timeline_update_volume', '0.5')
|
||||
self.config.set('sounds', 'notification_enabled', 'true')
|
||||
self.config.set('sounds', 'notification_volume', '0.8')
|
||||
|
||||
# Accessibility settings
|
||||
self.config.add_section('accessibility')
|
||||
self.config.set('accessibility', 'announce_thread_state', 'true')
|
||||
self.config.set('accessibility', 'auto_expand_mentions', 'false')
|
||||
self.config.set('accessibility', 'keyboard_navigation_wrap', 'true')
|
||||
self.config.set('accessibility', 'focus_follows_mouse', 'false')
|
||||
|
||||
# Interface settings
|
||||
self.config.add_section('interface')
|
||||
self.config.set('interface', 'default_timeline', 'home')
|
||||
self.config.set('interface', 'show_timestamps', 'true')
|
||||
self.config.set('interface', 'compact_mode', 'false')
|
||||
|
||||
self.save_settings()
|
||||
|
||||
def save_settings(self):
|
||||
"""Save settings to config file"""
|
||||
with open(self.config_file, 'w') as f:
|
||||
self.config.write(f)
|
||||
|
||||
def get(self, section: str, key: str, fallback: Any = None) -> Optional[str]:
|
||||
"""Get a setting value"""
|
||||
try:
|
||||
return self.config.get(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError):
|
||||
return fallback
|
||||
|
||||
def get_bool(self, section: str, key: str, fallback: bool = False) -> bool:
|
||||
"""Get a boolean setting value"""
|
||||
try:
|
||||
return self.config.getboolean(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
|
||||
return fallback
|
||||
|
||||
def get_int(self, section: str, key: str, fallback: int = 0) -> int:
|
||||
"""Get an integer setting value"""
|
||||
try:
|
||||
return self.config.getint(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
|
||||
return fallback
|
||||
|
||||
def get_float(self, section: str, key: str, fallback: float = 0.0) -> float:
|
||||
"""Get a float setting value"""
|
||||
try:
|
||||
return self.config.getfloat(section, key)
|
||||
except (configparser.NoSectionError, configparser.NoOptionError, ValueError):
|
||||
return fallback
|
||||
|
||||
def set(self, section: str, key: str, value: Any):
|
||||
"""Set a setting value"""
|
||||
if not self.config.has_section(section):
|
||||
self.config.add_section(section)
|
||||
self.config.set(section, key, str(value))
|
||||
|
||||
def get_sounds_dir(self) -> Path:
|
||||
"""Get the sounds directory path"""
|
||||
return self.data_dir / "sounds"
|
||||
|
||||
def get_current_sound_pack_dir(self) -> Path:
|
||||
"""Get the current sound pack directory"""
|
||||
pack_name = self.get('general', 'current_sound_pack', 'default')
|
||||
return self.get_sounds_dir() / pack_name
|
Reference in New Issue
Block a user