Istanza unica (lock), avvio automatico tramite LuZa.app, elenco dispositivi audio immediato su macOS, riuso delle porte del server remoto

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5.1 committed 2026-09-30 10:09:55 +02:00
1 parent 094807ffe8
commit bea863e011
4 files changed
+39 -7

No files matched your search

+3 -3
View File
@@ -175,11 +175,11 @@ class RemoteServer:
runner = web.AppRunner(app) runner = web.AppRunner(app)
self._loop.run_until_complete(runner.setup()) self._loop.run_until_complete(runner.setup())
try: try:
self._loop.run_until_complete(web.TCPSite(runner, "0.0.0.0", HTTPS_PORT, ssl_context=ctx).start()) self._loop.run_until_complete(web.TCPSite(runner, "0.0.0.0", HTTPS_PORT, ssl_context=ctx, reuse_address=True).start())
self._loop.run_until_complete(web.TCPSite(runner, "0.0.0.0", HTTP_PORT).start()) # solo CA e reindirizzo self._loop.run_until_complete(web.TCPSite(runner, "0.0.0.0", HTTP_PORT, reuse_address=True).start()) # solo CA e reindirizzo
except Exception as err: except Exception as err:
self.error = str(err) self.error = str(err)
self.log(f"ERR: Accesso remoto — porta occupata? {err}") self.log(f"ERR: Accesso remoto non avviato: porta {HTTPS_PORT} occupata (c'è un'altra LuZa aperta?). {err}")
return return
self.log(f"SYS: Accesso remoto pronto su https://{self.ip}:{HTTPS_PORT} (CA: http://{self.ip}:{HTTP_PORT}/ca.crt)") self.log(f"SYS: Accesso remoto pronto su https://{self.ip}:{HTTPS_PORT} (CA: http://{self.ip}:{HTTP_PORT}/ca.crt)")
try: try:
+5 -2
View File
@@ -271,6 +271,9 @@ def _query() -> dict[str, list[str]]:
apis = [] apis = []
preferred = _PREFERRED_APIS.get(platform.system(), ()) preferred = _PREFERRED_APIS.get(platform.system(), ())
# Su macOS Core Audio non mente: aprire un flusso di prova su ogni dispositivo (Bluetooth, AirPlay, schermi)
# costa secondi ciascuno e blocca l'interfaccia. Le prove servono solo per le API fasulle di Windows/Linux.
fast = platform.system() == "Darwin"
def _collect(api_filter, kind) -> list[tuple[int, str]]: def _collect(api_filter, kind) -> list[tuple[int, str]]:
"""(index, name) for named, non-pseudo devices on one side that can """(index, name) for named, non-pseudo devices on one side that can
@@ -287,7 +290,7 @@ def _query() -> dict[str, list[str]]:
api = apis[dev["hostapi"]].lower() if dev.get("hostapi", -1) < len(apis) else "" api = apis[dev["hostapi"]].lower() if dev.get("hostapi", -1) < len(apis) else ""
if api_filter not in api: if api_filter not in api:
continue continue
if not _usable(idx, kind): if not fast and not _usable(idx, kind):
continue continue
seen.add(name) seen.add(name)
found.append((idx, name)) found.append((idx, name))
@@ -303,7 +306,7 @@ def _query() -> dict[str, list[str]]:
if not found: if not found:
continue continue
# One probe per API per direction, cached, on this thread. # One probe per API per direction, cached, on this thread.
if not _transport_works(found[0][0], kind, (api_filter, kind)): if not fast and not _transport_works(found[0][0], kind, (api_filter, kind)):
continue continue
_chosen_api[kind] = api_filter _chosen_api[kind] = api_filter
out[kind] = [_display_name(n, devices) for _i, n in found] out[kind] = [_display_name(n, devices) for _i, n in found]
+28
View File
@@ -33,7 +33,30 @@ def ensure_identity_file() -> None:
f.write_text(json.dumps(data, indent=4, ensure_ascii=False), encoding="utf-8") f.write_text(json.dumps(data, indent=4, ensure_ascii=False), encoding="utf-8")
def _single_instance() -> bool:
"""Blocco esclusivo su data/luza.lock: una seconda LuZa (avvio automatico + avvio manuale) esce subito."""
import fcntl
from avatar.settings import DATA_DIR
DATA_DIR.mkdir(parents=True, exist_ok=True)
global _lock_file
_lock_file = open(DATA_DIR / "luza.lock", "w")
try:
fcntl.flock(_lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
_lock_file.write(str(__import__("os").getpid())); _lock_file.flush()
return True
except OSError:
return False
def main() -> None: def main() -> None:
if "--smoke" not in sys.argv and not _single_instance():
print("LuZa è già in esecuzione: questa seconda istanza si chiude.")
try:
import subprocess
subprocess.run(["osascript", "-e", 'display notification "LuZa è già aperta." with title "LuZa"'], timeout=5)
except Exception:
pass
return
ensure_identity_file() ensure_identity_file()
settings = Settings() settings = Settings()
try: try:
@@ -57,6 +80,11 @@ def main() -> None:
ui.on_wake_toggle = assistant.on_wake_toggle ui.on_wake_toggle = assistant.on_wake_toggle
ui.on_wake_manual = assistant.on_wake_manual ui.on_wake_manual = assistant.on_wake_manual
ui.wake_get_state = assistant.wake_get_state ui.wake_get_state = assistant.wake_get_state
try:
from core import audio_devices
audio_devices.prefetch() # elenco dispositivi audio pronto prima che si apra la finestra
except Exception:
pass
from avatar.plugins import registry from avatar.plugins import registry
from core import confirm from core import confirm
confirm.bind(ui.show_confirm, ui.hide_confirm, ui.write_log) confirm.bind(ui.show_confirm, ui.hide_confirm, ui.write_log)
+3 -2
View File
@@ -4691,8 +4691,9 @@ class MainWindow(QMainWindow):
'<plist version="1.0"><dict>\n' '<plist version="1.0"><dict>\n'
' <key>Label</key><string>it.luza.assistente</string>\n' ' <key>Label</key><string>it.luza.assistente</string>\n'
' <key>ProgramArguments</key><array>\n' ' <key>ProgramArguments</key><array>\n'
f' <string>{sys.executable}</string>\n' + (' <string>/usr/bin/open</string>\n <string>-a</string>\n <string>/Applications/LuZa.app</string>\n'
f' <string>{script}</string>\n' if Path("/Applications/LuZa.app").exists() else
f' <string>{sys.executable}</string>\n <string>{script}</string>\n') +
' </array>\n' ' </array>\n'
' <key>RunAtLoad</key><true/>\n' ' <key>RunAtLoad</key><true/>\n'
'</dict></plist>\n' '</dict></plist>\n'