diff --git a/proxy/tg_ws_proxy.py b/proxy/tg_ws_proxy.py index c3c00c6..b6d853e 100644 --- a/proxy/tg_ws_proxy.py +++ b/proxy/tg_ws_proxy.py @@ -596,7 +596,7 @@ async def _handle_client(reader, writer): rr, rw = await asyncio.wait_for( asyncio.open_connection(dst, port), timeout=10) except Exception as exc: - log.warning("[%s] passthrough failed to %s: %s", label, dst, exc) + log.warning("[%s] passthrough failed to %s: %s: %s", label, dst, type(exc).__name__, str(exc) or "(no message)") writer.write(_socks5_reply(0x05)) await writer.drain() writer.close() diff --git a/requirements-win7.txt b/requirements-win7.txt index 491d3f5..41a8174 100644 --- a/requirements-win7.txt +++ b/requirements-win7.txt @@ -3,3 +3,4 @@ customtkinter==5.2.2 Pillow==10.4.0 psutil==5.9.8 pystray==0.19.5 +pyperclip==1.9.0 diff --git a/requirements.txt b/requirements.txt index ba9706d..c7a85f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ customtkinter==5.2.2 Pillow==12.1.1 psutil==7.0.0 pystray==0.19.5 +pyperclip==1.9.0 diff --git a/windows.py b/windows.py index bd8d25f..e0bd25f 100644 --- a/windows.py +++ b/windows.py @@ -10,6 +10,7 @@ import threading import time import webbrowser import pystray +import pyperclip import asyncio as _asyncio import customtkinter as ctk from pathlib import Path @@ -42,13 +43,27 @@ _exiting: bool = False log = logging.getLogger("tg-ws-tray") -def is_already_running(): - current_proc = os.path.basename(sys.argv[0]) - count = 0 - for process in psutil.process_iter(['name']): - if process.info['name'] == current_proc: - count += 1 - return count > 2 +def _acquire_lock() -> bool: + _ensure_dirs() + lock_files = list(APP_DIR.glob("*.lock")) + + for f in lock_files: + try: + pid = int(f.stem) + if psutil.pid_exists(pid): + try: + psutil.Process(pid).status() + return False + except (psutil.NoSuchProcess, psutil.ZombieProcess): + pass + except Exception: + pass + + f.unlink(missing_ok=True) + + lock_file = APP_DIR / f"{os.getpid()}.lock" + lock_file.touch() + return True def _ensure_dirs(): @@ -148,6 +163,8 @@ def _run_proxy_thread(port: int, dc_opt: Dict[int, str], verbose: bool): tg_ws_proxy._run(port, dc_opt, stop_event=stop_ev)) except Exception as exc: log.error("Proxy thread crashed: %s", exc) + if "10048" in str(exc) or "Address already in use" in str(exc): + _show_error("Не удалось запустить прокси:\nПорт уже используется другим приложением.\n\nЗакройте приложение, использующее этот порт, или измените порт в настройках прокси и перезапустите.") finally: loop.close() _async_stop = None @@ -216,7 +233,7 @@ def _on_open_in_telegram(icon=None, item=None): except Exception: log.info("Browser open failed, copying to clipboard") try: - _copy_to_clipboard(url) + pyperclip.copy(url) _show_info( f"Не удалось открыть Telegram автоматически.\n\n" f"Ссылка скопирована в буфер обмена, отправьте её в телеграмм и нажмите по ней ЛКМ:\n{url}", @@ -226,31 +243,11 @@ def _on_open_in_telegram(icon=None, item=None): _show_error(f"Не удалось скопировать ссылку:\n{exc}") -def _copy_to_clipboard(text: str): - """Copy text to Windows clipboard using ctypes.""" - import ctypes.wintypes - CF_UNICODETEXT = 13 - kernel32 = ctypes.windll.kernel32 - user32 = ctypes.windll.user32 - - user32.OpenClipboard(0) - user32.EmptyClipboard() - - encoded = text.encode("utf-16-le") + b"\x00\x00" - h = kernel32.GlobalAlloc(0x0042, len(encoded)) # GMEM_MOVEABLE | GMEM_ZEROINIT - p = kernel32.GlobalLock(h) - ctypes.memmove(p, encoded, len(encoded)) - kernel32.GlobalUnlock(h) - user32.SetClipboardData(CF_UNICODETEXT, h) - user32.CloseClipboard() - - def _on_restart(icon=None, item=None): threading.Thread(target=restart_proxy, daemon=True).start() def _on_edit_config(icon=None, item=None): - """Open a simple dialog to edit config.""" threading.Thread(target=_edit_config_dialog, daemon=True).start() @@ -571,7 +568,7 @@ def run_tray(): def main(): - if is_already_running(): + if not _acquire_lock(): _show_info("Приложение уже запущено.", os.path.basename(sys.argv[0])) return