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>
127 lines
3.2 KiB
Python
127 lines
3.2 KiB
Python
# -*- mode: python ; coding: utf-8 -*-
|
|
"""PyInstaller Spec-Datei für SIP Softphone AppImage.
|
|
|
|
Bündelt Python-App + pjsua2 + Qt + alle nativen Abhängigkeiten
|
|
zu einem komplett unabhängigen Paket.
|
|
"""
|
|
|
|
import os
|
|
import subprocess
|
|
import re
|
|
|
|
# === Pfade ===
|
|
APP_DIR = os.path.dirname(os.path.abspath(SPEC))
|
|
PJSUA2_SO = "/tmp/sipwebapp_run/_pjsua2.cpython-314-x86_64-linux-gnu.so"
|
|
|
|
# === pjsua2 native Bibliotheken sammeln ===
|
|
# Alle Shared Libraries die _pjsua2.so braucht (nicht-triviale)
|
|
def pjsua2_libs_sammeln():
|
|
"""Alle pjsua2-Abhängigkeiten aus ldd extrahieren."""
|
|
ergebnis = subprocess.run(
|
|
["ldd", PJSUA2_SO], capture_output=True, text=True)
|
|
libs = []
|
|
# System-Basis-Libs die überall vorhanden sind (nicht bündeln)
|
|
skip = {
|
|
"linux-vdso", "ld-linux", "libc.so", "libm.so",
|
|
"libdl.so", "libpthread.so", "librt.so",
|
|
}
|
|
for zeile in ergebnis.stdout.splitlines():
|
|
match = re.match(r"\s+(\S+)\s+=>\s+(/\S+)", zeile)
|
|
if match:
|
|
name, pfad = match.groups()
|
|
if not any(s in name for s in skip) and os.path.exists(pfad):
|
|
libs.append((pfad, "."))
|
|
return libs
|
|
|
|
pjsua2_binaries = pjsua2_libs_sammeln()
|
|
|
|
# dbus native Libs
|
|
dbus_libs = []
|
|
for lib_name in ["libdbus-1.so.3", "libglib-2.0.so.0", "libgio-2.0.so.0",
|
|
"libgobject-2.0.so.0", "libgmodule-2.0.so.0"]:
|
|
for pfad in [f"/usr/lib/{lib_name}", f"/usr/lib/x86_64-linux-gnu/{lib_name}"]:
|
|
if os.path.exists(pfad):
|
|
dbus_libs.append((pfad, "."))
|
|
break
|
|
|
|
# === pjsua2 Python-Bindings ===
|
|
pjsua2_daten = [
|
|
(PJSUA2_SO, "."),
|
|
("/tmp/sipwebapp_run/pjsua2.py", "."),
|
|
]
|
|
|
|
# === dbus Python-Bindings ===
|
|
SYS_SITE = "/usr/lib/python3.14/site-packages"
|
|
dbus_daten = []
|
|
if os.path.isdir(f"{SYS_SITE}/dbus"):
|
|
dbus_daten.append((f"{SYS_SITE}/dbus", "dbus"))
|
|
for so in ["_dbus_bindings", "_dbus_glib_bindings"]:
|
|
for f in os.listdir(SYS_SITE):
|
|
if f.startswith(so) and f.endswith(".so"):
|
|
dbus_daten.append((f"{SYS_SITE}/{f}", "."))
|
|
|
|
# === Ressourcen ===
|
|
ressourcen = [
|
|
(os.path.join(APP_DIR, "resources"), "resources"),
|
|
]
|
|
|
|
# === Hidden Imports (Module die PyInstaller nicht automatisch findet) ===
|
|
hidden_imports = [
|
|
"pjsua2",
|
|
"_pjsua2",
|
|
"dbus",
|
|
"dbus.service",
|
|
"dbus.mainloop.glib",
|
|
"dbus._dbus",
|
|
"dbus.bus",
|
|
"dbus.connection",
|
|
"dbus.decorators",
|
|
"dbus.exceptions",
|
|
"dbus.lowlevel",
|
|
"dbus.proxies",
|
|
"dbus.types",
|
|
"vobject",
|
|
"vobject.vcard",
|
|
"vobject.base",
|
|
]
|
|
|
|
# === Analysis ===
|
|
a = Analysis(
|
|
[os.path.join(APP_DIR, "main.py")],
|
|
pathex=[APP_DIR],
|
|
binaries=pjsua2_binaries + dbus_libs + pjsua2_daten,
|
|
datas=ressourcen + dbus_daten,
|
|
hiddenimports=hidden_imports,
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[],
|
|
excludes=[
|
|
"tkinter", "unittest", "test", "xmlrpc",
|
|
"pydoc", "doctest", "lib2to3",
|
|
],
|
|
noarchive=False,
|
|
)
|
|
|
|
pyz = PYZ(a.pure)
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
[],
|
|
exclude_binaries=True,
|
|
name="sipwebapp",
|
|
debug=False,
|
|
bootloader_ignore_signals=False,
|
|
strip=True,
|
|
upx=False,
|
|
console=False, # Kein Terminal-Fenster
|
|
)
|
|
|
|
coll = COLLECT(
|
|
exe,
|
|
a.binaries,
|
|
a.datas,
|
|
strip=True,
|
|
upx=False,
|
|
name="sipwebapp",
|
|
)
|