linux.sipsoftphone/utils/klingelton.py
data 48ddb4b4af Initiales Release: SIP Softphone für FreePBX/Asterisk
PySide6/PJSUA2-basiertes Desktop-Softphone mit:
- SIP-Telefonie (Anrufe, DTMF, Halten, Transfer, Konferenz)
- CardDAV-Kontaktsync mit Write-Back (Multi-Account)
- Kontaktverwaltung mit Suche und Bearbeiten-Dialog
- Anrufliste mit Namensauflösung
- Favoriten-Panel (manuell + häufig angerufen)
- BLF-Panel (SIP Presence Monitoring)
- Responsives Layout (kompakt/erweitert)
- Click-to-Call (tel:/sip: URI via D-Bus)
- KDE-Integration (Tray, Benachrichtigungen)
- PipeWire/PulseAudio Audio-Routing
- AppImage Build-Support (PyInstaller + appimagetool)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:18:54 +01:00

108 lines
3.3 KiB
Python

"""Klingelton-Player - Spielt Klingelton über separates PipeWire-Gerät ab."""
import subprocess
from pathlib import Path
from PySide6.QtCore import QObject, QTimer
# Standard-Klingelton-Pfade
_STANDARD_KLINGELTON = Path(__file__).parent.parent / "resources" / "sounds" / "klingelton.wav"
_FREEDESKTOP_KLINGELTON = Path("/usr/share/sounds/freedesktop/stereo/phone-incoming-call.oga")
class KlingeltonPlayer(QObject):
"""Spielt Klingelton über paplay mit optionalem PipeWire-Gerät.
Nutzt paplay (PulseAudio/PipeWire CLI) mit --device für die
Ausgabe über ein dediziertes Klingelton-Gerät (z.B. Lautsprecher
statt Headset).
"""
def __init__(self, parent=None):
super().__init__(parent)
self._prozess = None
self._loop_timer = QTimer(self)
self._loop_timer.timeout.connect(self._erneut_abspielen)
self._datei = ""
self._device = ""
self._laeuft = False
def abspielen(self, datei_pfad="", device_pw_name="", loop=True):
"""Klingelton abspielen.
Args:
datei_pfad: Pfad zur Audio-Datei (leer = Standard)
device_pw_name: PipeWire-Sink-Name (leer = System-Standard)
loop: Endlosschleife bis stoppen() aufgerufen wird
"""
self.stoppen()
# Datei bestimmen
if datei_pfad and Path(datei_pfad).exists():
self._datei = datei_pfad
elif _STANDARD_KLINGELTON.exists():
self._datei = str(_STANDARD_KLINGELTON)
elif _FREEDESKTOP_KLINGELTON.exists():
self._datei = str(_FREEDESKTOP_KLINGELTON)
else:
return # Keine Audio-Datei verfügbar
self._device = device_pw_name
self._laeuft = True
self._paplay_starten()
if loop:
# Nach 5 Sekunden erneut abspielen (Klingelton-Datei ist ~5s)
self._loop_timer.start(5500)
def stoppen(self):
"""Klingelton stoppen."""
self._laeuft = False
self._loop_timer.stop()
if self._prozess and self._prozess.poll() is None:
try:
self._prozess.terminate()
except OSError:
pass
self._prozess = None
def test_abspielen(self, datei_pfad="", device_pw_name=""):
"""Einmal abspielen zum Testen (kein Loop)."""
self.abspielen(datei_pfad, device_pw_name, loop=False)
@property
def laeuft(self):
"""True wenn Klingelton gerade abgespielt wird."""
return self._laeuft
def _paplay_starten(self):
"""paplay Subprocess starten."""
if not self._laeuft:
return
cmd = ["paplay"]
if self._device:
cmd.extend(["--device", self._device])
cmd.append(self._datei)
try:
self._prozess = subprocess.Popen(
cmd,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except (FileNotFoundError, OSError):
self._laeuft = False
def _erneut_abspielen(self):
"""Timer-Callback: Klingelton in Schleife abspielen."""
if not self._laeuft:
self._loop_timer.stop()
return
# Vorherigen Prozess abräumen
if self._prozess and self._prozess.poll() is not None:
self._prozess = None
self._paplay_starten()