From ce29d5e5f4c82410b6d24ee2aa679b2487ce31a2 Mon Sep 17 00:00:00 2001 From: Luframe Date: Mon, 6 Apr 2026 18:57:33 +0200 Subject: [PATCH 1/2] =?UTF-8?q?Fix:=20R=C3=A9solution=20des=20conflits=20e?= =?UTF-8?q?t=20nettoyage=20des=20pycache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- app_version.py | 41 +++++++++ gui.py | 49 +++++++++++ main.py | 215 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + setup.iss | 42 ++++----- version.json | 4 + 7 files changed, 335 insertions(+), 22 deletions(-) create mode 100644 app_version.py diff --git a/.gitignore b/.gitignore index 42fd0f6..b614919 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -setup_dosoft.iss settings.json -build_dosoft.cmd __pychache__ build dist -main.spec +Dosoft.spec +installer_output diff --git a/app_version.py b/app_version.py new file mode 100644 index 0000000..0f25aee --- /dev/null +++ b/app_version.py @@ -0,0 +1,41 @@ +import json +import os +import sys + +APP_VERSION = "1.2.2" + + +def _candidate_paths(filename: str) -> list[str]: + paths = [] + if getattr(sys, "frozen", False): + base_dir = getattr(sys, "_MEIPASS", "") + if base_dir: + paths.append(os.path.join(base_dir, filename)) + paths.append(os.path.join(os.path.dirname(sys.executable), filename)) + paths.append(os.path.join(os.path.dirname(__file__), filename)) + return paths + + +def _read_version_from_json(path: str) -> str | None: + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + version = data.get("version") + if isinstance(version, str) and version.strip(): + return version.strip() + except Exception: + return None + return None + + +def get_current_version() -> str: + for path in _candidate_paths("version.json"): + if os.path.exists(path): + version = _read_version_from_json(path) + if version: + return version + return APP_VERSION + + +CURRENT_VERSION = get_current_version() +APP_TITLE = f"DOSOFT v{CURRENT_VERSION}" \ No newline at end of file diff --git a/gui.py b/gui.py index d110d90..3a4d79c 100644 --- a/gui.py +++ b/gui.py @@ -70,6 +70,26 @@ def __init__(self, parent_gui): self.var_keyboard_layout = ctk.StringVar(value=self.app.config.data.get("keyboard_layout", "azerty_fr")) ctk.CTkOptionMenu(frame_keyboard, values=["azerty_fr", "qwerty_us"], variable=self.var_keyboard_layout, command=lambda _: self.save_settings()).pack(side="right", padx=8, pady=8) + # --- BLOC MOTEUR NOTIFICATIONS (DEBUG) --- + frame_debug = ctk.CTkFrame(self.scroll_container) + frame_debug.pack(fill="x", padx=10, pady=5) + + self.lbl_debug = ctk.CTkLabel(frame_debug, text="Moteur Auto-Focus (Si bug)") + self.lbl_debug.pack(side="left", padx=8, pady=8) + + # On définit v2 par défaut + self.var_notif_api = ctk.StringVar(value=self.app.config.data.get("notif_api_mode", "v2")) + + # Le sélecteur avec les 3 versions + combo_debug = ctk.CTkOptionMenu( + frame_debug, + values=["v1", "v2", "v3"], + variable=self.var_notif_api, + command=self.on_notif_api_change + ) + combo_debug.pack(side="right", padx=8, pady=8) + self.parent.bind_i18n_tooltip(combo_debug, "tooltip_api_mode", "Changez de version si l'autofocus cesse de fonctionner.\nNécessite un redémarrage.") + self.btn_close = ctk.CTkButton(self.scroll_container, text=self.app.i18n.t("settings_close", "Fermer"), fg_color="#7f8c8d", command=self.destroy) self.btn_close.pack(pady=(20, 10)) @@ -84,9 +104,25 @@ def save_settings(self): self.app.config.save() self.parent.apply_translations() self.apply_translations() + self.app.config.data["notif_api_mode"] = self.var_notif_api.get() if previous_language != self.var_language.get(): self.title(self.app.i18n.t("settings_title", "⚙️ Paramètres")) + + def on_notif_api_change(self, choice): + self.app.config.data["notif_api_mode"] = choice + self.app.config.save() + + rep = messagebox.askyesno( + "Redémarrage requis", + "Pour appliquer le changement de moteur d'Autofocus, DOSOFT doit être redémarré", + parent=self + ) + if rep: + # On utilise ta fonction de fermeture native qui nettoie tout correctement + self.app.quit_app() + + def apply_translations(self): self.title(self.app.i18n.t("settings_title", "⚙️ Paramètres")) @@ -103,7 +139,11 @@ class OrganizerGUI: def __init__(self, app_controller): self.app = app_controller self.root = ctk.CTk() +<<<<<<< Updated upstream self.root.title(self.app.i18n.t("app_title", "DOSOFT v1.1.1")) +======= + self.root.title(APP_TITLE) +>>>>>>> Stashed changes screen_w = self.root.winfo_screenwidth() screen_h = self.root.winfo_screenheight() @@ -142,7 +182,11 @@ def __init__(self, app_controller): self.header_f = ctk.CTkFrame(self.root, fg_color="transparent") self.header_f.pack(fill="x", padx=15, pady=(15, 5)) +<<<<<<< Updated upstream self.lbl_app_title = ctk.CTkLabel(self.header_f, text=self.app.i18n.t("app_title", "DOSOFT v1.1.1"), font=ctk.CTkFont(size=20, weight="bold")) +======= + self.lbl_app_title = ctk.CTkLabel(self.header_f, text=APP_TITLE, font=ctk.CTkFont(size=20, weight="bold")) +>>>>>>> Stashed changes self.lbl_app_title.pack(side="left") self.btn_settings = ctk.CTkButton(self.header_f, text=self.app.i18n.t("header_settings", "⚙️ Paramètres"), fg_color="#34495e", hover_color="#2c3e50", width=120, command=self.open_settings) @@ -251,8 +295,13 @@ def __init__(self, app_controller): def apply_translations(self): none_label = self.app.i18n.t("none", "Aucun") +<<<<<<< Updated upstream self.root.title(self.app.i18n.t("app_title", "DOSOFT v1.1.1")) self.lbl_app_title.configure(text=self.app.i18n.t("app_title", "DOSOFT v1.1.1")) +======= + self.root.title(APP_TITLE) + self.lbl_app_title.configure(text=APP_TITLE) +>>>>>>> Stashed changes self.btn_settings.configure(text=self.app.i18n.t("header_settings", "⚙️ Paramètres")) self.btn_tuto.configure(text=self.app.i18n.t("header_tutorial", "🎓 Tuto")) self.btn_off.configure(text=self.app.i18n.t("header_off", "🔴 OFF")) diff --git a/main.py b/main.py index ca1ef7e..f1eebe4 100644 --- a/main.py +++ b/main.py @@ -438,10 +438,104 @@ def run_async_loop(): threading.Thread(target=run_async_loop, daemon=True).start() + def start_notification_listener(self): + if not WINSDK_AVAILABLE: return + + def run_async_loop(): + # Initialisation COM sécurisée + try: + import pythoncom + pythoncom.CoInitializeEx(0, pythoncom.COINIT_MULTITHREADED) + except Exception: + pass + + # Boucle de résurrection globale (utilisée par les 3 moteurs) + while True: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + loop.run_until_complete(self.poll_notifications()) + except Exception: + pass + finally: + try: loop.close() + except: pass + + # Pause de 3 secondes avant de relancer en cas de crash total + time.sleep(3) + + threading.Thread(target=run_async_loop, daemon=True).start() + async def poll_notifications(self): +<<<<<<< Updated upstream # Boucle globale pour relancer la connexion si le service Windows redémarre while True: try: +======= + # Aiguilleur : on lit le choix de l'utilisateur + api_mode = self.config.data.get("notif_api_mode", "v3") + + if api_mode == "v1": + await self._poll_notifications_v1() + elif api_mode == "v2": + await self._poll_notifications_v2() + elif api_mode == "v4": + await self._poll_notifications_v4() + else: + await self._poll_notifications_v3() + + # ========================================== + # MOTEUR V1 : SIMPLE (Aucun nettoyage) + # ========================================== + async def _poll_notifications_v1(self): + try: + listener = UserNotificationListener.current + access = await listener.request_access_async() + if access != 1: return + except Exception: return + + seen_ids = set() + first_pass = True + + while True: + try: + is_retro = self.config.data.get("game_version", "Unity") == "Rétro" + is_autofocus_on = self.config.data.get("auto_focus_retro", False) + + notifs = await listener.get_notifications_async(NotificationKinds.TOAST) + current_ids = set() + + for n in notifs: + current_ids.add(n.id) + if n.id not in seen_ids: + seen_ids.add(n.id) + if not first_pass and is_retro and is_autofocus_on: + try: + binding = n.notification.visual.bindings[0] + texts = [t.text for t in binding.get_text_elements()] + for ligne in texts: + if " - Dofus Retro" in ligne: + pseudo = ligne.split(" - ")[0].strip() + cycle_list = self.logic.get_cycle_list() + for index, acc in enumerate(cycle_list): + if acc['name'] == pseudo: + self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) + self.current_idx = index + break + break + except Exception: pass + seen_ids.intersection_update(current_ids) + first_pass = False + except Exception: pass + await asyncio.sleep(0.5) + + # ========================================== + # MOTEUR V2 : DOUBLE BOUCLE ET NETTOYAGE + # ========================================== + async def _poll_notifications_v2(self): + while True: + try: +>>>>>>> Stashed changes listener = UserNotificationListener.current access = await listener.request_access_async() if access != 1: return @@ -449,15 +543,23 @@ async def poll_notifications(self): seen_ids = set() first_pass = True +<<<<<<< Updated upstream # Fonction asynchrone pour effacer la notification de Windows après 1 seconde +======= +>>>>>>> Stashed changes async def remove_notif_delayed(notif_id): await asyncio.sleep(1.0) try: listener.remove_notification(notif_id) +<<<<<<< Updated upstream except Exception: pass # Boucle de lecture très rapide +======= + except Exception: pass + +>>>>>>> Stashed changes while True: try: is_retro = self.config.data.get("game_version", "Unity") == "Rétro" @@ -468,6 +570,7 @@ async def remove_notif_delayed(notif_id): for n in notifs: current_ids.add(n.id) +<<<<<<< Updated upstream # Si c'est une nouvelle notification... if n.id not in seen_ids: seen_ids.add(n.id) @@ -514,6 +617,114 @@ async def remove_notif_delayed(notif_id): # --- SYSTÈME DE VÉRIFICATION DE VERSION --- CURRENT_VERSION = "1.2.0" +======= + if n.id not in seen_ids: + seen_ids.add(n.id) + + if not first_pass and is_retro and is_autofocus_on: + try: + binding = n.notification.visual.bindings[0] + texts = [t.text for t in binding.get_text_elements()] + is_dofus_notif = False + + for ligne in texts: + if " - Dofus Retro" in ligne: + is_dofus_notif = True + pseudo = ligne.split(" - ")[0].strip() + cycle_list = self.logic.get_cycle_list() + for index, acc in enumerate(cycle_list): + if acc['name'] == pseudo: + self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) + self.current_idx = index + break + break + + if is_dofus_notif: + asyncio.create_task(remove_notif_delayed(n.id)) + + except Exception: pass + + seen_ids.intersection_update(current_ids) + first_pass = False + except Exception: break + await asyncio.sleep(0.5) + except Exception: + await asyncio.sleep(5) + + # ========================================== + # MOTEUR V3 : TIMEOUT ANTI-FREEZE + NETTOYAGE (Recommandé) + # ========================================== + async def _poll_notifications_v3(self): + try: + listener = UserNotificationListener.current + access = await listener.request_access_async() + if access != 1: + await asyncio.sleep(5) + return + except Exception: + await asyncio.sleep(5) + return + + seen_ids = set() + first_pass = True + + async def remove_notif_delayed(notif_id): + await asyncio.sleep(1.0) + try: listener.remove_notification(notif_id) + except Exception: pass + + while True: + try: + is_retro = self.config.data.get("game_version", "Unity") == "Rétro" + is_autofocus_on = self.config.data.get("auto_focus_retro", False) + + notifs = await asyncio.wait_for( + listener.get_notifications_async(NotificationKinds.TOAST), + timeout=3.0 + ) + + current_ids = set() + for n in notifs: + current_ids.add(n.id) + if n.id not in seen_ids: + seen_ids.add(n.id) + try: + binding = n.notification.visual.bindings[0] + texts = [t.text for t in binding.get_text_elements()] + is_dofus_notif = False + + for ligne in texts: + if " - Dofus Retro" in ligne: + is_dofus_notif = True + if not first_pass and is_retro and is_autofocus_on: + pseudo = ligne.split(" - ")[0].strip() + cycle_list = self.logic.get_cycle_list() + for index, acc in enumerate(cycle_list): + if acc['name'] == pseudo: + self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) + self.current_idx = index + break + break + + if is_dofus_notif: + asyncio.create_task(remove_notif_delayed(n.id)) + + except Exception: pass + + seen_ids.intersection_update(current_ids) + first_pass = False + + except asyncio.TimeoutError: + pass # Freeze de Windows ignoré + except Exception: + break # Crash API -> Redémarrage boucle + + await asyncio.sleep(0.5) + + + +# --- SYSTÈME DE VÉRIFICATION DE VERSION --- +>>>>>>> Stashed changes VERSION_URL = "https://raw.githubusercontent.com/LuframeCode/Dosoft/main/version.json" def check_version(i18n=None): @@ -566,7 +777,11 @@ def handle_multiple_instances(): root.attributes("-topmost", True) rep = messagebox.askyesno(i18n.t("header_instace_off", "Instance détectée"),i18n.t("popup_conflict_instance_text","Une instance de DOSOFT est déjà en cours d'exécution !\n\nVoulez-vous fermer l'ancienne instance pour ouvrir celle-ci ?")) if rep: +<<<<<<< Updated upstream hwnd = win32gui.FindWindow(None, "DOSOFT v1.2.0") +======= + hwnd = win32gui.FindWindow(None, APP_TITLE) +>>>>>>> Stashed changes if hwnd: _, pid = win32process.GetWindowThreadProcessId(hwnd) try: diff --git a/requirements.txt b/requirements.txt index 377df26..2be0143 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pystray pygame-ce Pillow requests +winsdk \ No newline at end of file diff --git a/setup.iss b/setup.iss index 1ca5371..ccfac64 100644 --- a/setup.iss +++ b/setup.iss @@ -1,7 +1,6 @@ ; ============================================================ ; DOSOFT - Script Inno Setup -; Prerequis : Inno Setup 6+ (https://jrsoftware.org/isinfo.php) -; A lancer APRES la compilation Nuitka (build_dosoft.cmd) +; Prerequis : Inno Setup 6+ ; ============================================================ #define AppName "Dosoft" @@ -9,6 +8,11 @@ #define AppPublisher "Dosoft" #define AppExeName "Dosoft.exe" #define SourceDir "dist" +<<<<<<< Updated upstream +======= +; Renseigne simplement ta version ici à chaque mise à jour : +#define AppVersion "1.2.2" +>>>>>>> Stashed changes [Setup] AppId={{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} @@ -46,50 +50,49 @@ UninstallDisplayIcon={app}\{#AppExeName} UninstallDisplayName={#AppName} VersionInfoVersion={#AppVersion} VersionInfoCompany={#AppPublisher} -VersionInfoDescription=Gestionnaire multi-compte Dofus - Dosoft +VersionInfoDescription=Gestionnaire multi-compte Dofus - {#AppName} [Languages] Name: "french"; MessagesFile: "compiler:Languages\French.isl" [Tasks] -Name: "desktopicon"; Description: "Créer un raccourci sur le Bureau"; GroupDescription: "Raccourcis :" +Name: "desktopicon"; Description: "Créer un raccourci sur le Bureau"; GroupDescription: "Raccourcis :" Name: "startmenuicon"; Description: "Créer un raccourci dans le Menu Démarrer"; GroupDescription: "Raccourcis :" [Files] -; --- Executable principal (compilé par Nuitka/PyInstaller) --- +; --- Executable principal --- Source: "{#SourceDir}\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion ; --- Ressources --- Source: "logo.ico"; DestDir: "{app}"; Flags: ignoreversion -Source: "skin\*"; DestDir: "{app}\skin"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "sounds\*"; DestDir: "{app}\sounds"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "skin\*"; DestDir: "{app}\skin"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "sounds\*"; DestDir: "{app}\sounds"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] -; --- Raccourci Bureau --- +; --- Raccourcis --- Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; IconFilename: "{app}\logo.ico"; Tasks: desktopicon +Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; IconFilename: "{app}\logo.ico"; Tasks: startmenuicon -; --- Raccourci Menu Démarrer --- -Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; IconFilename: "{app}\logo.ico"; Tasks: startmenuicon -Name: "{group}\Désinstaller {#AppName}"; Filename: "{uninstallexe}" +; CORRECTION ICI : On ajoute "Tasks: startmenuicon" à la fin de la ligne +Name: "{group}\Désinstaller {#AppName}"; Filename: "{uninstallexe}"; Tasks: startmenuicon [Run] ; --- Lancement après installation --- Filename: "{app}\{#AppExeName}"; Description: "Lancer {#AppName} maintenant"; Flags: nowait postinstall skipifsilent runascurrentuser -; [UninstallDelete] -; Type: files; Name: "{app}\settings.json" -; Type: filesandordirs; Name: "{app}" - [Code] function GetUninstallString(): String; var sUnInstPath: String; sUnInstallString: String; begin - sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1'); + // Chemin de désinstallation écrit en dur pour éviter les bugs d'accolades avec ExpandConstant + sUnInstPath := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}_is1'; sUnInstallString := ''; + if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString); + Result := sUnInstallString; end; @@ -126,22 +129,23 @@ begin BackupPath := ExpandConstant('{tmp}\settings_backup.json'); if (CurStep = ssInstall) then begin - // 1. On sauvegarde le fichier existant AVANT de désinstaller l'ancienne version + // 1. Sauvegarde du settings.json de l'ancienne version if FileExists(SettingsPath) then begin FileCopy(SettingsPath, BackupPath, False); end; + // Désinstallation silencieuse if (IsUpgrade()) then UnInstallOldVersion(); end; if (CurStep = ssPostInstall) then begin - // 2. On restaure le fichier dans le dossier tout neuf + // 2. Restauration du settings.json if FileExists(BackupPath) then begin FileCopy(BackupPath, SettingsPath, False); end; - // Après installation : exclusion Windows Defender pour éviter faux positifs + // Exclusion Windows Defender ExePath := ExpandConstant('{app}\{#AppExeName}'); Exec('powershell.exe', '-ExecutionPolicy Bypass -Command "Add-MpPreference -ExclusionPath ''' + ExePath + '''"', diff --git a/version.json b/version.json index af03d9f..3620365 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,8 @@ { +<<<<<<< Updated upstream "version": "1.1.1", +======= + "version": "1.2.2", +>>>>>>> Stashed changes "message": "Veuillez mettre à jour le depuis le DISCORD." } From 85de017e77640745cb7e44d1a3c025fb806f297f Mon Sep 17 00:00:00 2001 From: Luframe Date: Tue, 21 Apr 2026 18:17:44 +0200 Subject: [PATCH 2/2] fix: corrections critiques v1.2.2 - SyntaxError, NameError, v3 manquant, double corrections --- __pycache__/app_version.cpython-313.pyc | Bin 0 -> 2368 bytes __pycache__/config_manager.cpython-313.pyc | Bin 3215 -> 3149 bytes __pycache__/gui.cpython-313.pyc | Bin 77015 -> 78542 bytes __pycache__/i18n_manager.cpython-313.pyc | Bin 3203 -> 3203 bytes .../keyboard_layout_manager.cpython-313.pyc | Bin 4824 -> 4824 bytes __pycache__/logic.cpython-313.pyc | Bin 13733 -> 13667 bytes __pycache__/main.cpython-313.pyc | Bin 0 -> 45653 bytes __pycache__/radial_menu.cpython-313.pyc | Bin 12197 -> 12197 bytes gui.py | 6 +- main.py | 227 ++++++++++-------- setup.iss | 2 +- 11 files changed, 129 insertions(+), 106 deletions(-) create mode 100644 __pycache__/app_version.cpython-313.pyc create mode 100644 __pycache__/main.cpython-313.pyc diff --git a/__pycache__/app_version.cpython-313.pyc b/__pycache__/app_version.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e878ddb6b9e794579f94bdc131ef2a4d1422eeb GIT binary patch literal 2368 zcmd6o&2JM&6u@Uc>|J~P6`R;JiV`O=OphSR-|@w+B~Ptb70Hvr$t^H@e#fo4b)$TO`<7cvY~x6 zHm4}R`42KbK4;hy*c;fZ^2zj+B*!jDsu4@s87#-C`Z^-u6^McVmYXIH_am8_Y&@TY zxmpt`kscs+(E4fu#fk{M)Rqxh-BzZ)MCj zfx2%4b#J}@+1~%)jaB!dDVz5cvNdOQP4_U2{#QOb{+)k5^YS^pGnIqmq=p`K(lm7iqB+Y zXQw3CC`!PLM*R#XV5tVOLQ(m%F-3~P3Wdyiy-r?;hu?xY}0t70v6TC0GAE|BK)%TUr)_ z3&B6 z>AZQ-89GEV{e=d3TF)MtnKbDS!_1^s;K*3tp^?M8AcvXIOL9OjMh#2g>(!P%ltNwT z%|<;*fGRw2KuQ?ETJ+$u4+yzX<5m}{48s&H`_K zRtuO)B~=!Hi{Uo76op(rkmn$a zhh!fNw2F;^J?k>%Ne%E9kbu)KcDd^*1+i2vN8kt{$a{U=D#Rh3E zbo?+vGI}+eFJlj?A;ga$8<`4I&)E%=Z;1l5_>cfZG>L_JMUT%} z^m;)8;Wug(0$bo^wQw4xOKBymXc1C{k<+K6AB=^>6Q{=gv}RBXGs!nS$S&X|8lS;f zlCyfKF*pk>LV(|aE{7@(Z?WSi$6NDkMX)X&SU9kFXyH)7_fY6837r-9?)5~;y>I^A zU+wPskISBpE4C*l)Ut*}-(y5#pd_FJSSacd z5*{JbBV>MTYt0E&BTuyylqv#I%&s4~F}qFFs@p_4iak{XV!h`MyYa?0QL8(Rl&2tW fBM?uV$n|>3*_*SMZSJK{Hr>6OmcGj-&5OSQ!`7!N literal 0 HcmV?d00001 diff --git a/__pycache__/config_manager.cpython-313.pyc b/__pycache__/config_manager.cpython-313.pyc index db637552fd410cc1820127ae3fd1a99083cfe873..b77ce5dcda742961eff6e4384f60041b43125e53 100644 GIT binary patch delta 27 hcmeB|JS)NdnU|M~0SGQlf4-6XJ2Ru;W>*$34ghr|2o?YU delta 93 zcmX>r(J#sUnU|M~0SLGo&TZuW&g>lQY!wq)oLW>I6Izm)lUbZtl3H36 diff --git a/__pycache__/gui.cpython-313.pyc b/__pycache__/gui.cpython-313.pyc index a0938eced2602c5d5a3e3f8eb4782e123f64fa01..a0de13667428a3be8b0210c089dccb4c20a415c6 100644 GIT binary patch delta 13832 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 Vb+dwG99G_yd|UFD0+zWG_&*(XJx%}s delta 12438 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 WmUNq*rQdIOAn|*Nf7PKZdH)~EQ@&UL diff --git a/__pycache__/i18n_manager.cpython-313.pyc b/__pycache__/i18n_manager.cpython-313.pyc index ae310a27b5a89c3f4bbbe1060c09ee019900bc43..4e74c264c59e4a7e3fce06e5c7d2be53d14cf845 100644 GIT binary patch delta 27 hcmZpcY?kEy%*)Hg00ft&Ki|mB!OWPlS%f*C698q+2N?hW delta 27 hcmZpcY?kEy%*)Hg00h<(&u--AU}kjLEW(`62>@F~20{P; diff --git a/__pycache__/keyboard_layout_manager.cpython-313.pyc b/__pycache__/keyboard_layout_manager.cpython-313.pyc index a0ef19d0b925e051d65b6d69a67b4e0bf9b659ec..a8fa35d00217842709ec206188eac97d36eb4cd4 100644 GIT binary patch delta 27 hcmcbidP9}_GcPX}0}x!A{(K|%WM;;c&GVTp`2m0u2*CgV delta 27 hcmcbidP9}_GcPX}0}xnGJiC#5GBcyg=K0K)`~Y=|2kHO- diff --git a/__pycache__/logic.cpython-313.pyc b/__pycache__/logic.cpython-313.pyc index 15788bc88daefab6c27e70ba4c1091dada3cf481..55679fdd3f343e0f68df978cb5d4091e05ed8ed7 100644 GIT binary patch delta 27 hcmZ3Q{Wy#JGcPX}0}x!A{(K{MDpHc_QN_V*n}wBR2p5 diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b80f236ce2714378167d454c5083a89541c9f19 GIT binary patch literal 45653 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 O*?dQ-?(Gr{;{P9qXGc^3 literal 0 HcmV?d00001 diff --git a/__pycache__/radial_menu.cpython-313.pyc b/__pycache__/radial_menu.cpython-313.pyc index e58ec7b28547e38c7912c91d07696947c527752f..73d18a1d5c1f6696cd8802fb0f73103781ae1c84 100644 GIT binary patch delta 27 icmZ1)zcilvGcPX}0}x!A{(K|%4KBu%%}=-vssI3m{R#vC delta 27 hcmZ1)zcilvGcPX}0}zNZ9oWcygNxB+^AoOvDgbeU2x|ZU diff --git a/gui.py b/gui.py index a4942bf..a5c57b9 100644 --- a/gui.py +++ b/gui.py @@ -140,8 +140,6 @@ class OrganizerGUI: def __init__(self, app_controller): self.app = app_controller self.root = ctk.CTk() - self.root.title(self.app.i18n.t("app_title", "DOSOFT v1.1.1")) - self.root.title(APP_TITLE) screen_w = self.root.winfo_screenwidth() @@ -181,8 +179,6 @@ def __init__(self, app_controller): self.header_f = ctk.CTkFrame(self.root, fg_color="transparent") self.header_f.pack(fill="x", padx=15, pady=(15, 5)) - self.lbl_app_title = ctk.CTkLabel(self.header_f, text=self.app.i18n.t("app_title", "DOSOFT v1.1.1"), font=ctk.CTkFont(size=20, weight="bold")) - self.lbl_app_title = ctk.CTkLabel(self.header_f, text=APP_TITLE, font=ctk.CTkFont(size=20, weight="bold")) self.lbl_app_title.pack(side="left") @@ -336,7 +332,7 @@ def launch_tutorial(self): self.app.config.data["tutorial_done"] = True self.app.config.save() - rep = messagebox.askyesno(self.app.i18n.t("dialog_tutorial_title", "Tutoriel Vidéo"),self.app.i18n.t("dialog_tutorial_text", "Voulez-vous ouvrir la vidéo de présentation sur YouTube dans votre navigateur web ?"),self.app.i18n.t("button_yes", "Oui"),self.app.i18n.t("button_no", "Non")) + rep = messagebox.askyesno(self.app.i18n.t("dialog_tutorial_title", "Tutoriel Vidéo"), self.app.i18n.t("dialog_tutorial_text", "Voulez-vous ouvrir la vidéo de présentation sur YouTube dans votre navigateur web ?")) if rep: webbrowser.open("") diff --git a/main.py b/main.py index df19e9d..cc7eb07 100644 --- a/main.py +++ b/main.py @@ -425,98 +425,104 @@ def quit_app(self): os.system(f"taskkill /F /PID {my_pid} /T") def start_notification_listener(self): - if not WINSDK_AVAILABLE: return + if not WINSDK_AVAILABLE: + return def run_async_loop(): try: import pythoncom pythoncom.CoInitializeEx(0, pythoncom.COINIT_MULTITHREADED) - except: pass - - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - loop.run_until_complete(self.poll_notifications()) - - threading.Thread(target=run_async_loop, daemon=True).start() + except Exception: + pass - def start_notification_listener(self): - if not WINSDK_AVAILABLE: return + mode = self.config.data.get("notif_api_mode", "v2") + if mode == "v1": + engine = self.poll_notifications + elif mode == "v3": + engine = self._poll_notifications_v3 + else: + engine = self._poll_notifications_v2 - def run_async_loop(): - # Initialisation COM sécurisée - try: - import pythoncom - pythoncom.CoInitializeEx(0, pythoncom.COINIT_MULTITHREADED) - except Exception: - pass - - # Boucle de résurrection globale (utilisée par les 3 moteurs) while True: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: - loop.run_until_complete(self.poll_notifications()) + loop.run_until_complete(engine()) except Exception: pass finally: - try: loop.close() - except: pass - - # Pause de 3 secondes avant de relancer en cas de crash total + try: + loop.close() + except Exception: + pass time.sleep(3) threading.Thread(target=run_async_loop, daemon=True).start() async def poll_notifications(self): - # Boucle globale pour relancer la connexion si le service Windows redémarre while True: try: - is_retro = self.config.data.get("game_version", "Unity") == "Rétro" - is_autofocus_on = self.config.data.get("auto_focus_retro", False) - - notifs = await listener.get_notifications_async(NotificationKinds.TOAST) - current_ids = set() - - for n in notifs: - current_ids.add(n.id) - - if n.id not in seen_ids: - seen_ids.add(n.id) - if not first_pass and is_retro and is_autofocus_on: - try: - binding = n.notification.visual.bindings[0] - texts = [t.text for t in binding.get_text_elements()] - for ligne in texts: - if " - Dofus Retro" in ligne: - pseudo = ligne.split(" - ")[0].strip() - cycle_list = self.logic.get_cycle_list() - for index, acc in enumerate(cycle_list): - if acc['name'] == pseudo: - self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) - self.current_idx = index + listener = UserNotificationListener.current + access = await listener.request_access_async() + if access != 1: + await asyncio.sleep(5) + continue + + seen_ids = set() + first_pass = True + + while True: + try: + is_retro = self.config.data.get("game_version", "Unity") == "Rétro" + is_autofocus_on = self.config.data.get("auto_focus_retro", False) + notifs = await listener.get_notifications_async(NotificationKinds.TOAST) + current_ids = set() + + for n in notifs: + current_ids.add(n.id) + if n.id not in seen_ids: + seen_ids.add(n.id) + if not first_pass and is_retro and is_autofocus_on: + try: + binding = n.notification.visual.bindings[0] + texts = [t.text for t in binding.get_text_elements()] + for ligne in texts: + if " - Dofus Retro" in ligne: + pseudo = ligne.split(" - ")[0].strip() + cycle_list = self.logic.get_cycle_list() + for index, acc in enumerate(cycle_list): + if acc['name'] == pseudo: + self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) + self.current_idx = index + break break - break - except Exception: pass - seen_ids.intersection_update(current_ids) - first_pass = False - except Exception: pass - await asyncio.sleep(0.5) + except Exception: + pass + + seen_ids.intersection_update(current_ids) + first_pass = False + except Exception: + pass + await asyncio.sleep(0.5) + except Exception: + pass + await asyncio.sleep(3) # ========================================== - # MOTEUR V2 : DOUBLE BOUCLE ET NETTOYAGE + # MOTEUR V2 : AVEC NETTOYAGE NOTIFICATIONS # ========================================== async def _poll_notifications_v2(self): while True: try: - listener = UserNotificationListener.current access = await listener.request_access_async() - if access != 1: return + if access != 1: + await asyncio.sleep(5) + continue seen_ids = set() - first_pass = True + first_pass = True - # Fonction asynchrone pour effacer la notification de Windows après 1 seconde async def remove_notif_delayed(notif_id): await asyncio.sleep(1.0) try: @@ -524,75 +530,96 @@ async def remove_notif_delayed(notif_id): except Exception: pass - # Boucle de lecture très rapide while True: try: is_retro = self.config.data.get("game_version", "Unity") == "Rétro" is_autofocus_on = self.config.data.get("auto_focus_retro", False) - notifs = await listener.get_notifications_async(NotificationKinds.TOAST) current_ids = set() - + for n in notifs: current_ids.add(n.id) - # Si c'est une nouvelle notification... if n.id not in seen_ids: seen_ids.add(n.id) - if not first_pass and is_retro and is_autofocus_on: try: binding = n.notification.visual.bindings[0] texts = [t.text for t in binding.get_text_elements()] - is_dofus_notif = False - - # On vérifie si c'est le jeu qui parle for ligne in texts: if " - Dofus Retro" in ligne: is_dofus_notif = True pseudo = ligne.split(" - ")[0].strip() - cycle_list = self.logic.get_cycle_list() for index, acc in enumerate(cycle_list): if acc['name'] == pseudo: self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) self.current_idx = index break - break - - # NOUVEAU : On clear la notification SEULEMENT si c'est Dofus + break if is_dofus_notif: asyncio.create_task(remove_notif_delayed(n.id)) - - except Exception: pass - - # Auto-focus si ce n'est pas le 1er scan et que l'option est active - if not first_pass and is_retro and is_autofocus_on: - pseudo = ligne.split(" - ")[0].strip() - cycle_list = self.logic.get_cycle_list() - for index, acc in enumerate(cycle_list): - if acc['name'] == pseudo: - self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) - self.current_idx = index + except Exception: + pass + + seen_ids.intersection_update(current_ids) + first_pass = False + except Exception: + pass + await asyncio.sleep(0.5) + except Exception: + pass + await asyncio.sleep(3) + + # ========================================== + # MOTEUR V3 : POLLING RAPIDE SANS NETTOYAGE + # ========================================== + async def _poll_notifications_v3(self): + while True: + try: + listener = UserNotificationListener.current + access = await listener.request_access_async() + if access != 1: + await asyncio.sleep(5) + continue + + seen_ids = set() + first_pass = True + + while True: + is_retro = self.config.data.get("game_version", "Unity") == "Rétro" + is_autofocus_on = self.config.data.get("auto_focus_retro", False) + try: + notifs = await listener.get_notifications_async(NotificationKinds.TOAST) + current_ids = {n.id for n in notifs} + + for n in notifs: + if n.id not in seen_ids: + seen_ids.add(n.id) + if not first_pass and is_retro and is_autofocus_on: + try: + binding = n.notification.visual.bindings[0] + for t in binding.get_text_elements(): + if " - Dofus Retro" in t.text: + pseudo = t.text.split(" - ")[0].strip() + for index, acc in enumerate(self.logic.get_cycle_list()): + if acc['name'] == pseudo: + self.gui.root.after(0, self.logic.focus_window, acc['hwnd']) + self.current_idx = index + break break - break - - # Nettoyage automatique : on clear SEULEMENT les notifs Dofus Rétro - # (Même au first_pass, ça vide l'historique Windows pour éviter le crash) - if is_dofus_notif: - asyncio.create_task(remove_notif_delayed(n.id)) - - except Exception: pass - - seen_ids.intersection_update(current_ids) - first_pass = False - - except Exception: - # Si Windows sature un quart de seconde, on ignore et on continue + except Exception: + pass + + seen_ids.intersection_update(current_ids) + except Exception: + pass + + first_pass = False + await asyncio.sleep(0.3) + except Exception: pass - - # Focus ultra-réactif (0.5s) - await asyncio.sleep(0.5) + await asyncio.sleep(3) CURRENT_VERSION = "1.2.2" @@ -649,7 +676,7 @@ def handle_multiple_instances(): rep = messagebox.askyesno(i18n.t("header_instace_off", "Instance détectée"),i18n.t("popup_conflict_instance_text","Une instance de DOSOFT est déjà en cours d'exécution !\n\nVoulez-vous fermer l'ancienne instance pour ouvrir celle-ci ?")) if rep: - hwnd = win32gui.FindWindow(None, "DOSOFT v1.2.1") + hwnd = win32gui.FindWindow(None, APP_TITLE) if hwnd: _, pid = win32process.GetWindowThreadProcessId(hwnd) try: diff --git a/setup.iss b/setup.iss index 8ce4989..8075b2f 100644 --- a/setup.iss +++ b/setup.iss @@ -7,7 +7,7 @@ #define AppPublisher "Dosoft" #define AppExeName "Dosoft.exe" #define SourceDir "dist" -======= +#define SourceDir "dist" #define VersionFile "version.json" #define VersionRaw FileRead(VersionFile) #define VersionKey "\"version\": \""