mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-08-10 08:13:18 +03:00
MacOS moved to tkinter and pystray; cetifi implementation to fix SSL issues
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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.',
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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'",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user