8 Commits

Author SHA1 Message Date
Flowseal
03c7719c39 mutex check simplify 2026-04-14 16:58:54 +03:00
Flowseal
db4cebe0b2 build test 2026-04-14 16:51:26 +03:00
Flowseal
ca81d037f7 docs update 2026-04-14 03:11:13 +03:00
Flowseal
07615af49c bootloader build fix 2026-04-14 02:44:15 +03:00
Flowseal
f8ee37370d Version bump 2026-04-14 00:27:27 +03:00
Flowseal
4cbb9e555c windows mutex-lock 2026-04-14 00:27:27 +03:00
Flowseal
25ae4b0a24 build version changes 2026-04-14 00:27:27 +03:00
Kleshzz
8af1bc8c89 Add .gitattributes & Update .gitignore (#690) 2026-04-13 19:30:57 +03:00
10 changed files with 68 additions and 21 deletions

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
* text=auto eol=lf
*.py text diff=python
*.spec text linguist-language=Python
*.toml text
*.txt text
*.ico binary

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
python-version: "3.12" python-version: "3.11"
cache: "pip" cache: "pip"
- name: Setup MSVC 14.40 toolset - name: Setup MSVC 14.40 toolset
@@ -38,10 +38,11 @@ jobs:
run: pip install . run: pip install .
- name: Build PyInstaller bootloader from source - name: Build PyInstaller bootloader from source
run: |
pip install "pyinstaller==6.16.0" --no-binary pyinstaller
env: env:
PYINSTALLER_COMPILE_BOOTLOADER: 1 PYINSTALLER_COMPILE_BOOTLOADER: "1"
run: |
pip download --no-binary pyinstaller --no-deps --no-cache-dir -d pyinstaller_src "pyinstaller==6.10.0"
pip install (Get-ChildItem pyinstaller_src\*.tar.gz).FullName
- name: Build EXE with PyInstaller - name: Build EXE with PyInstaller
run: pyinstaller packaging/windows.spec --noconfirm run: pyinstaller packaging/windows.spec --noconfirm
@@ -193,7 +194,7 @@ jobs:
python3.12 -m pip install --no-deps wheelhouse/universal2/*.whl python3.12 -m pip install --no-deps wheelhouse/universal2/*.whl
python3.12 -m pip install . python3.12 -m pip install .
python3.12 -m pip install pyinstaller==6.16.0 python3.12 -m pip install pyinstaller==6.13.0
- name: Create macOS icon from ICO - name: Create macOS icon from ICO
run: | run: |
@@ -295,7 +296,7 @@ jobs:
run: | run: |
.venv/bin/pip install --upgrade pip .venv/bin/pip install --upgrade pip
.venv/bin/pip install . .venv/bin/pip install .
.venv/bin/pip install "pyinstaller==6.16.0" .venv/bin/pip install "pyinstaller==6.13.0"
- name: Build binary with PyInstaller - name: Build binary with PyInstaller
run: .venv/bin/pyinstaller packaging/linux.spec --noconfirm run: .venv/bin/pyinstaller packaging/linux.spec --noconfirm

2
.gitignore vendored
View File

@@ -6,6 +6,8 @@ __pycache__/
dist/ dist/
build/ build/
*.spec.bak *.spec.bak
venv/
.venv/
# PyInstaller # PyInstaller
*.manifest *.manifest

View File

@@ -11,13 +11,13 @@
> >
> ### Реакция антивирусов > ### Реакция антивирусов
> >
> Windows Defender часто ошибочно помечает приложение как **Wacatac**. > Антивирусы часто ошибочно помечают приложение как вирус из-за упаковщика.
> Если вы не можете скачать из-за блокировки, то: > Если вы не можете скачать из-за блокировки антивирусом, то:
> >
> 1) Попробуйте скачать версию win7 (она ничем не отличается в плане функционала) > 1) **Попробуйте скачать версию win7 (она ничем не отличается в плане функционала)**
> 2) Отключите антивирус на время скачивания, добавьте файл в исключения и включите обратно > 2) Отключите антивирус на время скачивания, добавьте файл в исключения и включите обратно
> >
> **Всегда проверяйте, что скачиваете из интернета, тем более из непроверенных источников. Всегда лучше смотреть на детекты широко известных антивирусов на VirusTotal** > Всегда проверяйте, что скачиваете из интернета, тем более из непроверенных источников. Всегда лучше смотреть на детекты широко известных антивирусов на VirusTotal
# TG WS Proxy # TG WS Proxy

View File

@@ -273,7 +273,7 @@ def run_tray() -> None:
def main() -> None: def main() -> None:
if not acquire_lock("linux.py"): if not acquire_lock():
_show_info("Приложение уже запущено.", os.path.basename(sys.argv[0])) _show_info("Приложение уже запущено.", os.path.basename(sys.argv[0]))
return return
try: try:

View File

@@ -610,7 +610,7 @@ def run_menubar() -> None:
def main() -> None: def main() -> None:
if not acquire_lock("macos.py"): if not acquire_lock():
_show_info("Приложение уже запущено.") _show_info("Приложение уже запущено.")
return return
try: try:

View File

@@ -4,8 +4,8 @@
# http://msdn.microsoft.com/en-us/library/ms646997.aspx # http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo( VSVersionInfo(
ffi=FixedFileInfo( ffi=FixedFileInfo(
filevers=(1, 0, 0, 0), filevers=(1, 6, 2, 0),
prodvers=(1, 0, 0, 0), prodvers=(1, 6, 2, 0),
mask=0x3f, mask=0x3f,
flags=0x0, flags=0x0,
OS=0x40004, OS=0x40004,
@@ -21,12 +21,12 @@ VSVersionInfo(
[ [
StringStruct(u'CompanyName', u'Flowseal'), StringStruct(u'CompanyName', u'Flowseal'),
StringStruct(u'FileDescription', u'Telegram Desktop WebSocket Bridge Proxy'), StringStruct(u'FileDescription', u'Telegram Desktop WebSocket Bridge Proxy'),
StringStruct(u'FileVersion', u'1.0.0.0'), StringStruct(u'FileVersion', u'1.6.2.0'),
StringStruct(u'InternalName', u'TgWsProxy'), StringStruct(u'InternalName', u'TgWsProxy'),
StringStruct(u'LegalCopyright', u'Copyright (c) Flowseal. MIT License.'), StringStruct(u'LegalCopyright', u'Copyright (c) Flowseal. MIT License.'),
StringStruct(u'OriginalFilename', u'TgWsProxy.exe'), StringStruct(u'OriginalFilename', u'TgWsProxy.exe'),
StringStruct(u'ProductName', u'TG WS Proxy'), StringStruct(u'ProductName', u'TG WS Proxy'),
StringStruct(u'ProductVersion', u'1.0.0.0'), StringStruct(u'ProductVersion', u'1.6.2.0'),
] ]
) )
] ]

View File

@@ -1,6 +1,6 @@
from .config import parse_dc_ip_list, proxy_config from .config import parse_dc_ip_list, proxy_config
from .utils import get_link_host from .utils import get_link_host
__version__ = "1.6.1" __version__ = "1.6.2"
__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list"] __all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list"]

View File

@@ -51,7 +51,7 @@ def ensure_dirs() -> None:
_lock_file_path: Optional[Path] = 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: try:
lock_ct = float(meta.get("create_time", 0.0)) lock_ct = float(meta.get("create_time", 0.0))
if lock_ct > 0 and abs(lock_ct - proc.create_time()) > 1.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 return False
def acquire_lock(script_hint: str = "") -> bool: def acquire_lock() -> bool:
global _lock_file_path global _lock_file_path
ensure_dirs() ensure_dirs()
for f in list(APP_DIR.glob("*.lock")): for f in list(APP_DIR.glob("*.lock")):
@@ -84,7 +84,7 @@ def acquire_lock(script_hint: str = "") -> bool:
pass pass
is_running = False is_running = False
try: try:
is_running = _same_process(meta, psutil.Process(pid), script_hint) is_running = _same_process(meta, psutil.Process(pid))
except Exception: except Exception:
pass pass
if is_running: if is_running:

View File

@@ -56,6 +56,39 @@ from ui.ctk_theme import (
_tray_icon: Optional[object] = None _tray_icon: Optional[object] = None
_config: dict = {} _config: dict = {}
_exiting = False _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") ICON_PATH = str(Path(__file__).parent / "icon.ico")
@@ -350,13 +383,15 @@ def run_tray() -> None:
def main() -> None: def main() -> None:
if not acquire_lock("windows.py"): if (mutex_result := _acquire_win_mutex()) is False or mutex_result is None and not acquire_lock():
_show_info("Приложение уже запущено.", os.path.basename(sys.argv[0])) _show_info("Приложение уже запущено.", os.path.basename(sys.argv[0]))
return return
try: try:
run_tray() run_tray()
finally: finally:
release_lock() release_lock()
_release_win_mutex()
if __name__ == "__main__": if __name__ == "__main__":