- Autostart: Desktop-Entry wird in ~/.config/autostart/ erstellt - Minimiert starten: App startet direkt im System-Tray - Einstellungen lesen Config direkt, kein CLI-Flag nötig - README.md aktualisiert Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""SIP Softphone - Desktop-Anwendung für FreePBX/Asterisk.
|
|
|
|
Basiert auf PJSUA2 (SIP-Stack) und PySide6 (Qt-GUI).
|
|
Verbindet sich über SIP/UDP direkt mit der FreePBX-Anlage.
|
|
|
|
Voraussetzungen:
|
|
- Python 3.10+
|
|
- PySide6: pip install PySide6
|
|
- pjsua2: yay -S python-pjproject (AUR, Manjaro/Arch)
|
|
|
|
Starten:
|
|
python3 main.py
|
|
|
|
Click-to-Call (tel: URI):
|
|
python3 main.py tel:+49123456789
|
|
python3 main.py sip:200@server
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
import signal
|
|
|
|
from PySide6.QtWidgets import QApplication
|
|
from PySide6.QtCore import Qt
|
|
|
|
# D-Bus Single-Instance
|
|
DBUS_SERVICE = "com.alleswattlaeuft.sipwebapp"
|
|
DBUS_PATH = "/com/alleswattlaeuft/sipwebapp"
|
|
|
|
try:
|
|
import dbus
|
|
import dbus.service
|
|
from dbus.mainloop.glib import DBusGMainLoop
|
|
DBUS_VERFUEGBAR = True
|
|
except ImportError:
|
|
DBUS_VERFUEGBAR = False
|
|
|
|
|
|
def _uri_zu_nummer(uri):
|
|
"""tel: oder sip: URI in Rufnummer umwandeln."""
|
|
if not uri:
|
|
return ""
|
|
# tel:+49123456789 → +49123456789
|
|
nummer = re.sub(r"^(tel:|sip:)", "", uri, flags=re.IGNORECASE)
|
|
# sip:200@server → 200
|
|
nummer = re.sub(r"@.*$", "", nummer)
|
|
# Leerzeichen und Sonderzeichen entfernen
|
|
nummer = re.sub(r"[^\d+*#]", "", nummer)
|
|
return nummer
|
|
|
|
|
|
def _uri_aus_args():
|
|
"""tel:/sip: URI aus Kommandozeilenargumenten extrahieren."""
|
|
for arg in sys.argv[1:]:
|
|
if arg.lower().startswith(("tel:", "sip:")):
|
|
return arg
|
|
return ""
|
|
|
|
|
|
def _minimiert_starten(config=None):
|
|
"""Prüft ob minimiert gestartet werden soll (Config oder Kommandozeile)."""
|
|
# Kommandozeile hat Vorrang (für manuelles Testen)
|
|
if "--minimiert" in sys.argv or "-m" in sys.argv:
|
|
return True
|
|
# Sonst aus Config lesen
|
|
if config:
|
|
return config.allgemein.get("minimiert_starten", False)
|
|
return False
|
|
|
|
|
|
class _DBusService(dbus.service.Object):
|
|
"""D-Bus Service für Single-Instance und URI-Weiterleitung."""
|
|
|
|
def __init__(self, bus_name, fenster):
|
|
super().__init__(bus_name, DBUS_PATH)
|
|
self._fenster = fenster
|
|
|
|
@dbus.service.method(DBUS_SERVICE, in_signature="s")
|
|
def anruf_von_uri(self, uri):
|
|
"""Von zweiter Instanz aufgerufen: URI → Anruf starten."""
|
|
nummer = _uri_zu_nummer(uri)
|
|
if nummer and self._fenster:
|
|
self._fenster.show()
|
|
self._fenster.activateWindow()
|
|
self._fenster.raise_()
|
|
# Nummer ins Wählfeld setzen und anrufen
|
|
self._fenster.waehlfeld.nummer_eingabe.setText(nummer)
|
|
self._fenster._anruf_mit_nummer(nummer)
|
|
|
|
|
|
def main():
|
|
# Ctrl+C im Terminal erlauben
|
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
|
|
uri = _uri_aus_args()
|
|
|
|
# D-Bus Mainloop MUSS vor allem anderen initialisiert werden
|
|
if DBUS_VERFUEGBAR:
|
|
DBusGMainLoop(set_as_default=True)
|
|
|
|
# D-Bus Single-Instance: Prüfen ob bereits eine Instanz läuft
|
|
if DBUS_VERFUEGBAR and uri:
|
|
try:
|
|
bus = dbus.SessionBus()
|
|
proxy = bus.get_object(DBUS_SERVICE, DBUS_PATH)
|
|
iface = dbus.Interface(proxy, DBUS_SERVICE)
|
|
iface.anruf_von_uri(uri)
|
|
# Erfolgreich an laufende Instanz weitergeleitet
|
|
sys.exit(0)
|
|
except dbus.DBusException:
|
|
pass # Keine laufende Instanz → normal starten
|
|
|
|
app = QApplication(sys.argv)
|
|
app.setApplicationName("SIP Softphone")
|
|
app.setOrganizationName("AllesWattLaeuft")
|
|
app.setApplicationVersion("1.0.0")
|
|
|
|
# High-DPI-Unterstützung
|
|
app.setHighDpiScaleFactorRoundingPolicy(
|
|
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
|
)
|
|
|
|
from ui.hauptfenster import HauptFenster
|
|
from utils.config_manager import ConfigManager
|
|
|
|
# Config laden für minimiert_starten Einstellung
|
|
config = ConfigManager()
|
|
|
|
fenster = HauptFenster()
|
|
|
|
# D-Bus Service registrieren (für Click-to-Call von Browser)
|
|
dbus_service = None
|
|
if DBUS_VERFUEGBAR:
|
|
try:
|
|
bus = dbus.SessionBus()
|
|
bus_name = dbus.service.BusName(DBUS_SERVICE, bus=bus)
|
|
dbus_service = _DBusService(bus_name, fenster)
|
|
except dbus.DBusException:
|
|
pass
|
|
|
|
# Minimiert starten? (aus Config oder --minimiert Flag)
|
|
minimiert = _minimiert_starten(config)
|
|
if minimiert:
|
|
# Direkt in Tray starten, Fenster nicht anzeigen
|
|
fenster.hide()
|
|
else:
|
|
fenster.show()
|
|
fenster.starten()
|
|
|
|
# Wenn mit URI gestartet, Anruf auslösen
|
|
if uri:
|
|
nummer = _uri_zu_nummer(uri)
|
|
if nummer:
|
|
# Kurz warten bis Registrierung durch ist
|
|
from PySide6.QtCore import QTimer
|
|
QTimer.singleShot(2000, lambda: fenster._anruf_mit_nummer(nummer))
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|