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 480 additions and 544 deletions
+7
View File
@@ -303,6 +303,12 @@ jobs:
python3.12 -m pip install . python3.12 -m pip install .
python3.12 -m pip install pyinstaller==6.13.0 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 - name: Create macOS icon
run: | run: |
set -euo pipefail set -euo pipefail
@@ -318,6 +324,7 @@ jobs:
'dist/TG WS Proxy.app/Contents/Info.plist')" 'dist/TG WS Proxy.app/Contents/Info.plist')"
test -n "$ICON_FILE" test -n "$ICON_FILE"
test -f "dist/TG WS Proxy.app/Contents/Resources/$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 found=0
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
+2
View File
@@ -20,6 +20,8 @@ tg-ws-proxy-tray-win
### macOS ### macOS
Требуется сборка Python с поддержкой Tk. Проверить её можно командой `python3 -m tkinter`.
```bash ```bash
pip install -e . pip install -e .
tg-ws-proxy-tray-macos tg-ws-proxy-tray-macos
+2
View File
@@ -24,6 +24,8 @@
Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md) Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md)
Для интерфейса требуются Tk, CustomTkinter и доступ к Cocoa через PyObjC. Они устанавливаются автоматически, кроме Tk, который должен входить в используемую сборку Python.
```bash ```bash
pip install -e . pip install -e .
tg-ws-proxy-tray-macos tg-ws-proxy-tray-macos
+428 -527
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 # customtkinter ships JSON themes + assets that must be bundled
import customtkinter import customtkinter
ctk_path = os.path.dirname(customtkinter.__file__) 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') _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')], [os.path.join(os.path.dirname(SPEC), os.pardir, 'linux.py')],
pathex=[], pathex=[],
binaries=[], 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=[ hiddenimports=[
'pystray._appindicator', 'pystray._appindicator',
'PIL._tkinter_finder', 'PIL._tkinter_finder',
+13 -9
View File
@@ -1,24 +1,31 @@
# -*- mode: python ; coding: utf-8 -*-
import sys import sys
import os import os
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None 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') _i18n_path = os.path.join(os.path.dirname(SPEC), os.pardir, 'ui', 'i18n')
a = Analysis( a = Analysis(
[os.path.join(os.path.dirname(SPEC), os.pardir, 'macos.py')], [os.path.join(os.path.dirname(SPEC), os.pardir, 'macos.py')],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[(_i18n_path, 'ui/i18n')], datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas,
hiddenimports=[ hiddenimports=[
'rumps', 'tkinter',
'customtkinter',
'pystray._darwin',
'PIL._tkinter_finder',
'objc', 'objc',
'Foundation', 'Foundation',
'AppKit', 'AppKit',
'PyObjCTools', 'PyObjCTools',
'PyObjCTools.AppHelper', 'PyObjCTools.MachSignals',
'cryptography.hazmat.primitives.ciphers', 'cryptography.hazmat.primitives.ciphers',
'cryptography.hazmat.primitives.ciphers.algorithms', 'cryptography.hazmat.primitives.ciphers.algorithms',
'cryptography.hazmat.primitives.ciphers.modes', 'cryptography.hazmat.primitives.ciphers.modes',
@@ -30,14 +37,13 @@ a = Analysis(
excludes=[ excludes=[
'PIL._avif', 'PIL._avif',
'PIL._webp', 'PIL._webp',
'PIL._imagingtk',
], ],
noarchive=False, noarchive=False,
cipher=block_cipher, cipher=block_cipher,
) )
_PIL_EXCLUDE_PYDS = { _PIL_EXCLUDE_PYDS = {
'_avif', '_webp', '_imagingtk', '_avif', '_webp',
'FpxImagePlugin', 'MicImagePlugin', 'FpxImagePlugin', 'MicImagePlugin',
} }
a.binaries = [ a.binaries = [
@@ -93,7 +99,5 @@ app = BUNDLE(
'LSMinimumSystemVersion': '10.15', 'LSMinimumSystemVersion': '10.15',
'LSUIElement': True, 'LSUIElement': True,
'NSHighResolutionCapable': True, 'NSHighResolutionCapable': True,
'NSAppleEventsUsageDescription':
'TG WS Proxy needs to display dialogs.',
}, },
) )
+4 -1
View File
@@ -3,11 +3,14 @@
import sys import sys
import os import os
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None block_cipher = None
# customtkinter ships JSON themes + assets that must be bundled # customtkinter ships JSON themes + assets that must be bundled
import customtkinter import customtkinter
ctk_path = os.path.dirname(customtkinter.__file__) 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') _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')], [os.path.join(os.path.dirname(SPEC), os.pardir, 'windows.py')],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')], datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas,
hiddenimports=[ hiddenimports=[
'pystray._win32', 'pystray._win32',
'PIL._tkinter_finder', 'PIL._tkinter_finder',
+6 -2
View File
@@ -1,6 +1,9 @@
import socket as _socket import socket as _socket
import urllib.request import urllib.request
import http.client import http.client
import ssl
import certifi
from typing import Optional, Dict, List from typing import Optional, Dict, List
from urllib.request import Request from urllib.request import Request
@@ -104,10 +107,11 @@ class _PinnedHTTPSHandler(urllib.request.HTTPSHandler):
) )
try: try:
return self.do_open(_Conn, req) return self.do_open(_Conn, req, context=self._context)
except Exception: except Exception:
return super().https_open(req) return super().https_open(req)
def build_github_opener() -> urllib.request.OpenerDirector: 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))
+4 -3
View File
@@ -36,6 +36,7 @@ classifiers = [
dependencies = [ dependencies = [
"pyperclip==1.9.0", "pyperclip==1.9.0",
"certifi==2026.7.22",
"psutil==5.9.8; platform_system == 'Windows' and python_version < '3.9'", "psutil==5.9.8; platform_system == 'Windows' and python_version < '3.9'",
"cryptography==41.0.7; 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'", "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'", "Pillow==12.1.1; (platform_system != 'Windows' or python_version >= '3.9') and platform_system != 'Darwin'",
"customtkinter==5.2.2; platform_system != 'Darwin'", "customtkinter==5.2.2",
"pystray==0.19.5; platform_system != 'Darwin'", "pystray==0.19.5",
"rumps==0.4.0; platform_system == 'Darwin'", "pyobjc-framework-Cocoa>=9.0; platform_system == 'Darwin'",
"Pillow==12.1.0; platform_system == 'Darwin'", "Pillow==12.1.0; platform_system == 'Darwin'",
] ]
+11
View File
@@ -323,6 +323,17 @@ def _run_proxy_thread(show_error: Callable[[str], None]) -> None:
if diagnose_called: if diagnose_called:
diagnose_called() diagnose_called()
finally: 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() loop.close()
_async_stop = None _async_stop = None