linux.sipsoftphone/main.py
data b0fd6889a3 feat: Autostart + Minimiert starten Funktionalität
- 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>
2026-02-25 11:28:55 +01:00

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()