diff --git a/linux.py b/linux.py index 3fa8bc8..45b6eda 100644 --- a/linux.py +++ b/linux.py @@ -273,7 +273,7 @@ def run_tray() -> None: def main() -> None: - if not acquire_lock("linux.py"): + if not acquire_lock(): _show_info("Приложение уже запущено.", os.path.basename(sys.argv[0])) return try: diff --git a/macos.py b/macos.py index 78a1180..e0d7f79 100644 --- a/macos.py +++ b/macos.py @@ -610,7 +610,7 @@ def run_menubar() -> None: def main() -> None: - if not acquire_lock("macos.py"): + if not acquire_lock(): _show_info("Приложение уже запущено.") return try: diff --git a/utils/tray_common.py b/utils/tray_common.py index ed559fd..7124d1f 100644 --- a/utils/tray_common.py +++ b/utils/tray_common.py @@ -51,7 +51,7 @@ def ensure_dirs() -> None: _lock_file_path: Optional[Path] = None -def _same_process(meta: dict, proc: psutil.Process, script_hint: str) -> bool: +def _same_process(meta: dict, proc: psutil.Process) -> bool: try: lock_ct = float(meta.get("create_time", 0.0)) if lock_ct > 0 and abs(lock_ct - proc.create_time()) > 1.0: @@ -63,7 +63,7 @@ def _same_process(meta: dict, proc: psutil.Process, script_hint: str) -> bool: return False -def acquire_lock(script_hint: str = "") -> bool: +def acquire_lock() -> bool: global _lock_file_path ensure_dirs() for f in list(APP_DIR.glob("*.lock")): @@ -84,7 +84,7 @@ def acquire_lock(script_hint: str = "") -> bool: pass is_running = False try: - is_running = _same_process(meta, psutil.Process(pid), script_hint) + is_running = _same_process(meta, psutil.Process(pid)) except Exception: pass if is_running: diff --git a/windows.py b/windows.py index 70a93d3..b489492 100644 --- a/windows.py +++ b/windows.py @@ -56,6 +56,39 @@ from ui.ctk_theme import ( _tray_icon: Optional[object] = None _config: dict = {} _exiting = False +_win_mutex_handle = None + +_ERROR_ALREADY_EXISTS = 183 + + +def _acquire_win_mutex() -> bool | None: + global _win_mutex_handle + try: + kernel32 = ctypes.windll.kernel32 + kernel32.CreateMutexW.restype = ctypes.c_void_p + kernel32.CreateMutexW.argtypes = [ctypes.c_void_p, ctypes.c_bool, ctypes.c_wchar_p] + handle = kernel32.CreateMutexW(None, True, "Local\\TgWsProxy_SingleInstance") + if kernel32.GetLastError() == _ERROR_ALREADY_EXISTS: + kernel32.CloseHandle(ctypes.c_void_p(handle)) + return False + if not handle: + return None + _win_mutex_handle = handle + return True + except Exception: + return None + + +def _release_win_mutex() -> None: + global _win_mutex_handle + if _win_mutex_handle: + try: + kernel32 = ctypes.windll.kernel32 + kernel32.ReleaseMutex(ctypes.c_void_p(_win_mutex_handle)) + kernel32.CloseHandle(ctypes.c_void_p(_win_mutex_handle)) + except Exception: + pass + _win_mutex_handle = None ICON_PATH = str(Path(__file__).parent / "icon.ico") @@ -350,13 +383,20 @@ def run_tray() -> None: def main() -> None: - if not acquire_lock("windows.py"): + if mutex_result := _acquire_win_mutex() is False: _show_info("Приложение уже запущено.", os.path.basename(sys.argv[0])) return + if mutex_result is None: + log.warning("Named mutex unavailable, falling back to lock file") + if not acquire_lock(): + _show_info("Приложение уже запущено.", os.path.basename(sys.argv[0])) + return + try: run_tray() finally: release_lock() + _release_win_mutex() if __name__ == "__main__":