MacOS moved to tkinter and pystray; cetifi implementation to fix SSL issues

This commit is contained in:
Flowseal
2026-08-07 00:41:20 +03:00
parent 2496004c93
commit 9f9e1c2482
10 changed files with 479 additions and 543 deletions
+7
View File
@@ -303,6 +303,12 @@ jobs:
python3.12 -m pip install .
python3.12 -m pip install pyinstaller==6.13.0
- name: Validate macOS GUI dependencies
run: |
python3.12 -m pip check
python3.12 -m py_compile macos.py
python3.12 -c "import AppKit, customtkinter, macos, pystray, tkinter"
- name: Create macOS icon
run: |
set -euo pipefail
@@ -318,6 +324,7 @@ jobs:
'dist/TG WS Proxy.app/Contents/Info.plist')"
test -n "$ICON_FILE"
test -f "dist/TG WS Proxy.app/Contents/Resources/$ICON_FILE"
test -d "dist/TG WS Proxy.app/Contents/Resources/customtkinter"
found=0
while IFS= read -r -d '' file; do
+2
View File
@@ -20,6 +20,8 @@ tg-ws-proxy-tray-win
### macOS
Требуется сборка Python с поддержкой Tk. Проверить её можно командой `python3 -m tkinter`.
```bash
pip install -e .
tg-ws-proxy-tray-macos
+2
View File
@@ -24,6 +24,8 @@
Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md)
Для интерфейса требуются Tk, CustomTkinter и доступ к Cocoa через PyObjC. Они устанавливаются автоматически, кроме Tk, который должен входить в используемую сборку Python.
```bash
pip install -e .
tg-ws-proxy-tray-macos
+427 -526
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -11,6 +11,7 @@ block_cipher = None
# customtkinter ships JSON themes + assets that must be bundled
import customtkinter
ctk_path = os.path.dirname(customtkinter.__file__)
certifi_datas = collect_data_files('certifi')
_i18n_path = os.path.join(os.path.dirname(SPEC), os.pardir, 'ui', 'i18n')
@@ -28,7 +29,7 @@ a = Analysis(
[os.path.join(os.path.dirname(SPEC), os.pardir, 'linux.py')],
pathex=[],
binaries=[],
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + gi_datas + typelib_datas,
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas + gi_datas + typelib_datas,
hiddenimports=[
'pystray._appindicator',
'PIL._tkinter_finder',
+13 -9
View File
@@ -1,24 +1,31 @@
# -*- mode: python ; coding: utf-8 -*-
import sys
import os
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None
import customtkinter
ctk_path = os.path.dirname(customtkinter.__file__)
certifi_datas = collect_data_files('certifi')
_i18n_path = os.path.join(os.path.dirname(SPEC), os.pardir, 'ui', 'i18n')
a = Analysis(
[os.path.join(os.path.dirname(SPEC), os.pardir, 'macos.py')],
pathex=[],
binaries=[],
datas=[(_i18n_path, 'ui/i18n')],
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas,
hiddenimports=[
'rumps',
'tkinter',
'customtkinter',
'pystray._darwin',
'PIL._tkinter_finder',
'objc',
'Foundation',
'AppKit',
'PyObjCTools',
'PyObjCTools.AppHelper',
'PyObjCTools.MachSignals',
'cryptography.hazmat.primitives.ciphers',
'cryptography.hazmat.primitives.ciphers.algorithms',
'cryptography.hazmat.primitives.ciphers.modes',
@@ -30,14 +37,13 @@ a = Analysis(
excludes=[
'PIL._avif',
'PIL._webp',
'PIL._imagingtk',
],
noarchive=False,
cipher=block_cipher,
)
_PIL_EXCLUDE_PYDS = {
'_avif', '_webp', '_imagingtk',
'_avif', '_webp',
'FpxImagePlugin', 'MicImagePlugin',
}
a.binaries = [
@@ -93,7 +99,5 @@ app = BUNDLE(
'LSMinimumSystemVersion': '10.15',
'LSUIElement': True,
'NSHighResolutionCapable': True,
'NSAppleEventsUsageDescription':
'TG WS Proxy needs to display dialogs.',
},
)
+4 -1
View File
@@ -3,11 +3,14 @@
import sys
import os
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None
# customtkinter ships JSON themes + assets that must be bundled
import customtkinter
ctk_path = os.path.dirname(customtkinter.__file__)
certifi_datas = collect_data_files('certifi')
_i18n_path = os.path.join(os.path.dirname(SPEC), os.pardir, 'ui', 'i18n')
@@ -15,7 +18,7 @@ a = Analysis(
[os.path.join(os.path.dirname(SPEC), os.pardir, 'windows.py')],
pathex=[],
binaries=[],
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')],
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas,
hiddenimports=[
'pystray._win32',
'PIL._tkinter_finder',
+6 -2
View File
@@ -1,6 +1,9 @@
import socket as _socket
import urllib.request
import http.client
import ssl
import certifi
from typing import Optional, Dict, List
from urllib.request import Request
@@ -104,10 +107,11 @@ class _PinnedHTTPSHandler(urllib.request.HTTPSHandler):
)
try:
return self.do_open(_Conn, req)
return self.do_open(_Conn, req, context=self._context)
except Exception:
return super().https_open(req)
def build_github_opener() -> urllib.request.OpenerDirector:
return urllib.request.build_opener(_PinnedHTTPSHandler())
context = ssl.create_default_context(cafile=certifi.where())
return urllib.request.build_opener(_PinnedHTTPSHandler(context=context))
+5 -4
View File
@@ -36,6 +36,7 @@ classifiers = [
dependencies = [
"pyperclip==1.9.0",
"certifi==2026.7.22",
"psutil==5.9.8; platform_system == 'Windows' and python_version < '3.9'",
"cryptography==41.0.7; platform_system == 'Windows' and python_version < '3.9'",
@@ -45,9 +46,9 @@ dependencies = [
"cryptography==46.0.5; platform_system != 'Windows' or python_version >= '3.9'",
"Pillow==12.1.1; (platform_system != 'Windows' or python_version >= '3.9') and platform_system != 'Darwin'",
"customtkinter==5.2.2; platform_system != 'Darwin'",
"pystray==0.19.5; platform_system != 'Darwin'",
"rumps==0.4.0; platform_system == 'Darwin'",
"customtkinter==5.2.2",
"pystray==0.19.5",
"pyobjc-framework-Cocoa>=9.0; platform_system == 'Darwin'",
"Pillow==12.1.0; platform_system == 'Darwin'",
]
@@ -73,4 +74,4 @@ packages = ["proxy", "ui", "utils"]
path = "proxy/__init__.py"
[tool.ruff.lint]
ignore = ["F403", "F405"]
ignore = ["F403", "F405"]
+11
View File
@@ -323,6 +323,17 @@ def _run_proxy_thread(show_error: Callable[[str], None]) -> None:
if diagnose_called:
diagnose_called()
finally:
pending = [
task for task in asyncio.all_tasks(loop)
if not task.done()
]
for task in pending:
task.cancel()
if pending:
loop.run_until_complete(asyncio.gather(
*pending, return_exceptions=True
))
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
_async_stop = None