diff --git a/README.md b/README.md index 2502fdb..68296ce 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ Basiert auf **PJSUA2** (SIP-Stack) und **PySide6** (Qt-GUI). Verbindet sich übe - **Responsives Layout**: Kompakt (nur Wählfeld) oder Breit (Wählfeld + Tabs) - **Click-to-Call**: `tel:` und `sip:` URI-Handler via D-Bus Single-Instance - **KDE-Integration**: System-Tray, D-Bus-Benachrichtigungen mit Annehmen/Ablehnen +- **Autostart**: Automatisch beim Systemstart starten (XDG-konform) +- **Minimiert starten**: Optional direkt in den System-Tray starten - **PipeWire-Audio**: Natives PipeWire/PulseAudio-Routing, separates Klingelton-Gerät - **AppImage**: Komplett unabhängiges Paket (kein Python/Qt auf dem Zielsystem nötig) @@ -207,6 +209,14 @@ Gespeichert unter `~/.config/sipwebapp/config.json`: - CardDAV-Accounts (Multi-Account) - BLF-Extensions - Favoriten (manuell gepinnt) +- Allgemein: Autostart, Minimiert starten, Tray-Verhalten + +### Autostart + +Wenn in den Einstellungen aktiviert, wird ein Desktop-Entry erstellt: +`~/.config/autostart/sipwebapp.desktop` + +Die Option "Minimiert starten" lässt die App direkt im System-Tray starten, ohne das Hauptfenster anzuzeigen. ## AppImage bauen diff --git a/main.py b/main.py index a2568e9..53dfd09 100644 --- a/main.py +++ b/main.py @@ -58,6 +58,17 @@ def _uri_aus_args(): 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.""" @@ -111,6 +122,11 @@ def main(): ) 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) @@ -123,7 +139,13 @@ def main(): except dbus.DBusException: pass - fenster.show() + # 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 diff --git a/ui/einstellungen.py b/ui/einstellungen.py index 6492a83..422ac30 100644 --- a/ui/einstellungen.py +++ b/ui/einstellungen.py @@ -178,6 +178,10 @@ class EinstellungenDialog(QDialog): "Beim Systemstart automatisch starten") form.addRow(self.autostart_check) + self.minimiert_starten_check = QCheckBox( + "Minimiert starten (direkt in System-Tray)") + form.addRow(self.minimiert_starten_check) + # Klingelton-Datei klingelton_layout = QHBoxLayout() self.klingelton_eingabe = QLineEdit() @@ -289,6 +293,8 @@ class EinstellungenDialog(QDialog): allg = self._config.allgemein self.tray_check.setChecked(allg.get("minimieren_in_tray", True)) self.autostart_check.setChecked(allg.get("autostart", False)) + self.minimiert_starten_check.setChecked( + allg.get("minimiert_starten", False)) self.klingelton_eingabe.setText(allg.get("klingelton", "")) # CardDAV-Accounts laden @@ -462,6 +468,8 @@ class EinstellungenDialog(QDialog): self.tray_check.isChecked()) self._config.set("allgemein", "autostart", self.autostart_check.isChecked()) + self._config.set("allgemein", "minimiert_starten", + self.minimiert_starten_check.isChecked()) self._config.set("allgemein", "klingelton", self.klingelton_eingabe.text()) @@ -478,5 +486,7 @@ class EinstellungenDialog(QDialog): self._config.set("blf", "extensions", extensions) self._config.speichern() + # Autostart Desktop-Entry erstellen/entfernen + self._config.autostart_aktualisieren() self.einstellungen_geaendert.emit() self.accept() diff --git a/utils/config_manager.py b/utils/config_manager.py index 59483d5..72550c6 100644 --- a/utils/config_manager.py +++ b/utils/config_manager.py @@ -2,6 +2,8 @@ import json import os +import shutil +import sys from pathlib import Path @@ -10,6 +12,13 @@ KONFIG_VERZEICHNIS = Path( os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config") ) / "sipwebapp" +# Autostart-Verzeichnis (XDG-Standard) +AUTOSTART_VERZEICHNIS = Path( + os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config") +) / "autostart" + +AUTOSTART_DATEI = AUTOSTART_VERZEICHNIS / "sipwebapp.desktop" + KONFIG_DATEI = KONFIG_VERZEICHNIS / "config.json" ANRUFLISTE_DATEI = KONFIG_VERZEICHNIS / "anrufliste.json" KONTAKTE_DATEI = KONFIG_VERZEICHNIS / "kontakte.json" @@ -33,6 +42,7 @@ STANDARD_KONFIG = { "allgemein": { "minimieren_in_tray": True, "autostart": False, + "minimiert_starten": False, # Beim Autostart minimiert in Tray starten "klingelton": "", # Pfad zu WAV-Datei }, "carddav": { @@ -202,3 +212,48 @@ class ConfigManager: KONFIG_VERZEICHNIS.mkdir(parents=True, exist_ok=True) with open(KONTAKTE_DATEI, "w", encoding="utf-8") as f: json.dump(kontakte, f, indent=2, ensure_ascii=False) + + # --- Autostart --- + + def autostart_aktivieren(self): + """Desktop-Entry für Autostart erstellen.""" + AUTOSTART_VERZEICHNIS.mkdir(parents=True, exist_ok=True) + + # Executable ermitteln + if getattr(sys, 'frozen', False): + # PyInstaller/AppImage: sys.executable ist das Binary + exec_path = sys.executable + else: + # Entwicklung: Python + main.py + main_py = Path(__file__).parent.parent / "main.py" + exec_path = f"{sys.executable} {main_py}" + + # Kein --minimiert Flag nötig - App liest selbst aus Config + + desktop_entry = f"""[Desktop Entry] +Type=Application +Version=1.0 +Name=SIP Softphone +Comment=SIP-Softphone für FreePBX/Asterisk +GenericName=Internet-Telefonie +Exec={exec_path} +Icon=sipwebapp +Categories=Network;Telephony; +StartupNotify=false +Terminal=false +X-GNOME-Autostart-enabled=true +""" + with open(AUTOSTART_DATEI, "w", encoding="utf-8") as f: + f.write(desktop_entry) + + def autostart_deaktivieren(self): + """Desktop-Entry für Autostart entfernen.""" + if AUTOSTART_DATEI.exists(): + AUTOSTART_DATEI.unlink() + + def autostart_aktualisieren(self): + """Autostart-Eintrag basierend auf Konfiguration erstellen oder entfernen.""" + if self.allgemein.get("autostart", False): + self.autostart_aktivieren() + else: + self.autostart_deaktivieren()