#!/usr/bin/env python3 import gi gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') from gi.repository import Gtk, Adw, Gio, GLib import sys import subprocess import json import os import threading import time import re class ComfyUILauncher(Gtk.ApplicationWindow): def __init__(self, **kwargs): super().__init__(**kwargs) # Configurazione finestra principale self.set_title("ComfyUI Launcher") self.set_default_size(900, 700) # Variabili di stato self.comfyui_process = None self.is_running = False self.selected_conda_env = None self.conda_envs = [] self.status_check_timeout = None # Crea il layout principale self.setup_ui() # Inizializza il controllo degli ambienti conda self.load_conda_environments() # Avvia il monitoraggio dello stato self.start_status_monitoring() def setup_ui(self): # Header bar header = Gtk.HeaderBar() header.set_title_widget(Gtk.Label(label="ComfyUI Launcher")) self.set_titlebar(header) # Menu button menu_button = Gtk.MenuButton() menu_button.set_icon_name("open-menu-symbolic") header.pack_end(menu_button) # Main content area main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) main_box.set_margin_top(12) main_box.set_margin_bottom(12) main_box.set_margin_start(12) main_box.set_margin_end(12) # Title title_label = Gtk.Label() title_label.set_markup("ComfyUI Launcher") title_label.set_margin_bottom(12) main_box.append(title_label) # Status section status_frame = Gtk.Frame() status_frame.set_label("Stato ComfyUI") status_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) status_box.set_margin_top(12) status_box.set_margin_bottom(12) status_box.set_margin_start(12) status_box.set_margin_end(12) self.status_indicator = Gtk.Image() self.status_indicator.set_from_icon_name("media-playback-stop-symbolic") self.status_indicator.set_icon_size(Gtk.IconSize.LARGE) status_box.append(self.status_indicator) self.status_label = Gtk.Label(label="ComfyUI non in esecuzione") self.status_label.set_hexpand(True) self.status_label.set_halign(Gtk.Align.START) status_box.append(self.status_label) status_frame.set_child(status_box) main_box.append(status_frame) # Environment selection env_frame = Gtk.Frame() env_frame.set_label("Selezione Ambiente Conda") env_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) env_box.set_margin_top(12) env_box.set_margin_bottom(12) env_box.set_margin_start(12) env_box.set_margin_end(12) # Dropdown per ambienti conda self.env_dropdown = Gtk.DropDown() self.env_dropdown.set_enable_search(True) self.env_dropdown.connect("notify::selected-item", self.on_env_selected) env_box.append(self.env_dropdown) # Refresh button refresh_button = Gtk.Button(label="Aggiorna Lista Ambienti") refresh_button.connect("clicked", self.on_refresh_envs) env_box.append(refresh_button) env_frame.set_child(env_box) main_box.append(env_frame) # Control buttons control_frame = Gtk.Frame() control_frame.set_label("Controlli") control_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) control_box.set_margin_top(12) control_box.set_margin_bottom(12) control_box.set_margin_start(12) control_box.set_margin_end(12) control_box.set_homogeneous(True) self.start_button = Gtk.Button(label="Avvia ComfyUI") self.start_button.add_css_class("suggested-action") self.start_button.connect("clicked", self.on_start_comfyui) self.start_button.set_sensitive(False) control_box.append(self.start_button) self.stop_button = Gtk.Button(label="Ferma ComfyUI") self.stop_button.add_css_class("destructive-action") self.stop_button.connect("clicked", self.on_stop_comfyui) self.stop_button.set_sensitive(False) control_box.append(self.stop_button) self.install_button = Gtk.Button(label="Installa ComfyUI") self.install_button.connect("clicked", self.on_install_comfyui) control_box.append(self.install_button) control_frame.set_child(control_box) main_box.append(control_frame) # Log section log_frame = Gtk.Frame() log_frame.set_label("Log") log_frame.set_vexpand(True) scrolled = Gtk.ScrolledWindow() scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled.set_vexpand(True) self.log_view = Gtk.TextView() self.log_view.set_editable(False) self.log_view.set_monospace(True) self.log_buffer = self.log_view.get_buffer() scrolled.set_child(self.log_view) log_frame.set_child(scrolled) main_box.append(log_frame) self.set_child(main_box) # Setup menu self.setup_menu(menu_button) def setup_menu(self, menu_button): menu = Gio.Menu() menu.append("Apri Cartella ComfyUI", "app.open_folder") menu.append("About", "app.about") menu.append("Quit", "app.quit") menu_button.set_menu_model(menu) def log_message(self, message): """Aggiunge un messaggio al log""" def update_log(): current_text = self.log_buffer.get_text( self.log_buffer.get_start_iter(), self.log_buffer.get_end_iter(), False ) timestamp = time.strftime("%H:%M:%S") new_text = f"{current_text}[{timestamp}] {message}\n" if current_text else f"[{timestamp}] {message}\n" self.log_buffer.set_text(new_text) # Scorri alla fine mark = self.log_buffer.get_insert() self.log_view.scroll_mark_onscreen(mark) GLib.idle_add(update_log) def load_conda_environments(self): """Carica la lista degli ambienti conda disponibili""" def load_envs(): try: # Prova a ottenere la lista degli ambienti conda result = subprocess.run(['conda', 'env', 'list', '--json'], capture_output=True, text=True, timeout=30) if result.returncode == 0: env_data = json.loads(result.stdout) envs = [] for env_path in env_data['envs']: env_name = os.path.basename(env_path) if env_name == env_path: # È il path base, usa 'base' env_name = 'base' envs.append((env_name, env_path)) def update_ui(): self.conda_envs = envs string_list = Gtk.StringList() for name, path in envs: string_list.append(f"{name} ({path})") self.env_dropdown.set_model(string_list) if envs: self.env_dropdown.set_selected(0) self.log_message(f"Trovati {len(envs)} ambienti conda") GLib.idle_add(update_ui) else: GLib.idle_add(lambda: self.log_message(f"Errore nel caricare ambienti conda: {result.stderr}")) except subprocess.TimeoutExpired: GLib.idle_add(lambda: self.log_message("Timeout nel caricamento ambienti conda")) except FileNotFoundError: GLib.idle_add(lambda: self.log_message("Conda non trovato. Assicurati che conda sia installato e nel PATH")) except Exception as e: GLib.idle_add(lambda: self.log_message(f"Errore imprevisto: {str(e)}")) # Esegui in un thread separato per non bloccare l'UI threading.Thread(target=load_envs, daemon=True).start() def on_env_selected(self, dropdown, param): """Gestisce la selezione di un ambiente conda""" selected_idx = dropdown.get_selected() if selected_idx != Gtk.INVALID_LIST_POSITION and self.conda_envs: env_name, env_path = self.conda_envs[selected_idx] self.selected_conda_env = (env_name, env_path) self.log_message(f"Selezionato ambiente: {env_name}") self.update_button_states() def on_refresh_envs(self, button): """Aggiorna la lista degli ambienti conda""" self.log_message("Aggiornamento lista ambienti...") self.load_conda_environments() def update_button_states(self): """Aggiorna lo stato dei pulsanti in base allo stato corrente""" has_env = self.selected_conda_env is not None self.start_button.set_sensitive(has_env and not self.is_running) self.stop_button.set_sensitive(self.is_running) self.install_button.set_sensitive(has_env) def check_comfyui_running(self): """Controlla se ComfyUI è in esecuzione""" try: # Controlla se il processo ComfyUI è ancora vivo if self.comfyui_process and self.comfyui_process.poll() is None: return True # Controlla se c'è un processo ComfyUI in esecuzione (porta 8188) try: result = subprocess.run(['ss', '-tulpn'], capture_output=True, text=True, timeout=5) if ':8188' in result.stdout: return True except: # Fallback con netstat se ss non è disponibile try: result = subprocess.run(['netstat', '-tulpn'], capture_output=True, text=True, timeout=5) if ':8188' in result.stdout: return True except: pass # Controlla processi con nome 'comfy' o 'python' che potrebbero essere ComfyUI try: result = subprocess.run(['pgrep', '-f', 'comfy.*launch'], capture_output=True, text=True, timeout=5) if result.returncode == 0 and result.stdout.strip(): return True except: pass return False except Exception as e: self.log_message(f"Errore nel controllo stato: {str(e)}") return False def update_status_ui(self): """Aggiorna l'interfaccia utente con lo stato corrente""" if self.is_running: self.status_indicator.set_from_icon_name("media-playback-start-symbolic") self.status_indicator.add_css_class("success") self.status_label.set_text("ComfyUI in esecuzione") else: self.status_indicator.set_from_icon_name("media-playback-stop-symbolic") self.status_indicator.remove_css_class("success") self.status_label.set_text("ComfyUI non in esecuzione") self.update_button_states() def start_status_monitoring(self): """Avvia il monitoraggio periodico dello stato""" def monitor(): was_running = self.is_running self.is_running = self.check_comfyui_running() if was_running != self.is_running: if self.is_running: self.log_message("ComfyUI rilevato in esecuzione") else: self.log_message("ComfyUI non più in esecuzione") GLib.idle_add(self.update_status_ui) return True # Continua il monitoring # Controlla ogni 3 secondi self.status_check_timeout = GLib.timeout_add(3000, monitor) def on_start_comfyui(self, button): """Avvia ComfyUI""" if not self.selected_conda_env: self.log_message("Nessun ambiente conda selezionato") return env_name, env_path = self.selected_conda_env self.log_message(f"Avvio ComfyUI nell'ambiente {env_name}...") def start_comfy(): try: # Comando per attivare l'ambiente conda e avviare comfyui if env_name == 'base': cmd = ['conda', 'run', '-n', 'base', 'comfy', 'launch'] else: cmd = ['conda', 'run', '-n', env_name, 'comfy', 'launch'] self.comfyui_process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True ) GLib.idle_add(lambda: self.log_message("Processo ComfyUI avviato")) # Leggi l'output in tempo reale for line in iter(self.comfyui_process.stdout.readline, ''): if line: GLib.idle_add(lambda l=line.strip(): self.log_message(f"ComfyUI: {l}")) except Exception as e: GLib.idle_add(lambda: self.log_message(f"Errore nell'avvio: {str(e)}")) threading.Thread(target=start_comfy, daemon=True).start() def on_stop_comfyui(self, button): """Ferma ComfyUI""" self.log_message("Fermando ComfyUI...") if self.comfyui_process: try: self.comfyui_process.terminate() # Aspetta un po' per la terminazione graceful try: self.comfyui_process.wait(timeout=5) except subprocess.TimeoutExpired: # Forza la terminazione self.comfyui_process.kill() self.comfyui_process = None self.log_message("Processo ComfyUI terminato") except Exception as e: self.log_message(f"Errore nella terminazione: {str(e)}") else: # Prova a killare qualsiasi processo sulla porta 8188 try: subprocess.run(['pkill', '-f', 'comfy'], timeout=5) self.log_message("Terminati processi ComfyUI") except: self.log_message("Nessun processo ComfyUI trovato da terminare") def on_install_comfyui(self, button): """Installa ComfyUI nell'ambiente selezionato""" if not self.selected_conda_env: self.log_message("Nessun ambiente conda selezionato") return env_name, env_path = self.selected_conda_env self.log_message(f"Installazione ComfyUI nell'ambiente {env_name}...") def install_comfy(): try: # Prima installa comfy-cli cmd1 = ['conda', 'run', '-n', env_name, 'pip', 'install', 'comfy-cli'] result1 = subprocess.run(cmd1, capture_output=True, text=True, timeout=300) if result1.returncode == 0: GLib.idle_add(lambda: self.log_message("comfy-cli installato con successo")) # Poi installa ComfyUI cmd2 = ['conda', 'run', '-n', env_name, 'comfy', 'install'] result2 = subprocess.run(cmd2, capture_output=True, text=True, timeout=600) if result2.returncode == 0: GLib.idle_add(lambda: self.log_message("ComfyUI installato con successo!")) else: GLib.idle_add(lambda: self.log_message(f"Errore installazione ComfyUI: {result2.stderr}")) else: GLib.idle_add(lambda: self.log_message(f"Errore installazione comfy-cli: {result1.stderr}")) except subprocess.TimeoutExpired: GLib.idle_add(lambda: self.log_message("Timeout durante l'installazione")) except Exception as e: GLib.idle_add(lambda: self.log_message(f"Errore durante l'installazione: {str(e)}")) threading.Thread(target=install_comfy, daemon=True).start() class ComfyUIApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) # Configura il style manager per gestire correttamente i temi self.setup_style_manager() # Aggiungi azioni per il menu self.setup_actions() def setup_style_manager(self): """Configura il gestore dello stile per evitare warning""" style_manager = Adw.StyleManager.get_default() style_manager.set_color_scheme(Adw.ColorScheme.DEFAULT) def setup_actions(self): # Azione About about_action = Gio.SimpleAction.new("about", None) about_action.connect("activate", self.on_about_action) self.add_action(about_action) # Azione Quit quit_action = Gio.SimpleAction.new("quit", None) quit_action.connect("activate", self.on_quit_action) self.add_action(quit_action) # Azione Open Folder open_folder_action = Gio.SimpleAction.new("open_folder", None) open_folder_action.connect("activate", self.on_open_folder_action) self.add_action(open_folder_action) # Shortcut per quit self.set_accels_for_action("app.quit", ["q"]) def on_activate(self, app): print("Avvio ComfyUI Launcher...") # Crea la finestra principale self.win = ComfyUILauncher(application=app) print("Finestra launcher creata") self.win.present() print("Launcher presentato") def on_about_action(self, action, param): # Mostra dialog About about = Adw.AboutWindow( transient_for=self.win, application_name="ComfyUI Launcher", application_icon="application-x-executable", developer_name="ComfyUI Launcher", version="1.0.0", developers=["ComfyUI Launcher Team"], copyright="© 2025 ComfyUI Launcher", comments="Un launcher GTK per ComfyUI con supporto conda" ) about.present() def on_open_folder_action(self, action, param): """Apre la cartella ComfyUI""" try: # Prova ad aprire la cartella di default di ComfyUI home_dir = os.path.expanduser("~") comfy_dir = os.path.join(home_dir, "comfy", "ComfyUI") if os.path.exists(comfy_dir): subprocess.run(['xdg-open', comfy_dir]) else: self.win.log_message("Cartella ComfyUI non trovata") except Exception as e: self.win.log_message(f"Errore nell'apertura cartella: {str(e)}") def on_quit_action(self, action, param): # Ferma ComfyUI se in esecuzione prima di chiudere if hasattr(self, 'win') and self.win.comfyui_process: self.win.on_stop_comfyui(None) self.quit() def main(): print("Avvio del ComfyUI Launcher...") try: app = ComfyUIApp(application_id="com.example.ComfyUILauncher") print("Applicazione launcher creata, avvio in corso...") exit_code = app.run(sys.argv) print(f"Launcher terminato con codice: {exit_code}") return exit_code except Exception as e: print(f"Errore durante l'esecuzione del launcher: {e}") import traceback traceback.print_exc() return 1 if __name__ == '__main__': main()