pyloid 0.18.2__tar.gz → 0.20.2.dev2__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/LICENSE +1 -1
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/PKG-INFO +5 -3
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/README.md +1 -1
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/pyproject.toml +2 -1
- pyloid-0.20.2.dev2/src/pyloid/__init__.py +8 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/browser_window.py +64 -23
- pyloid-0.20.2.dev2/src/pyloid/builder/__init__.py +81 -0
- pyloid-0.20.2.dev2/src/pyloid/builder/build_config.schema.json +73 -0
- pyloid-0.20.2.dev2/src/pyloid/builder/spec.py +266 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/utils.py +22 -10
- pyloid-0.18.2/src/pyloid/__init__.py +0 -7
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/api.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/autostart.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/custom/titlebar.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/filewatcher.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/js_api/event_api.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/js_api/window_api.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/monitor.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/pyloid.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/thread_pool.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/timer.py +0 -0
- {pyloid-0.18.2 → pyloid-0.20.2.dev2}/src/pyloid/tray.py +0 -0
@@ -198,4 +198,4 @@ Apache License
|
|
198
198
|
distributed under the License is distributed on an "AS IS" BASIS,
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
200
200
|
See the License for the specific language governing permissions and
|
201
|
-
limitations under the License.
|
201
|
+
limitations under the License.
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.3
|
2
2
|
Name: pyloid
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.20.2.dev2
|
4
4
|
Summary:
|
5
5
|
Author: aesthetics-of-record
|
6
6
|
Author-email: 111675679+aesthetics-of-record@users.noreply.github.com
|
@@ -10,6 +10,8 @@ Classifier: Programming Language :: Python :: 3.9
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
14
|
+
Requires-Dist: pyinstaller (>=6.11.1,<7.0.0)
|
13
15
|
Requires-Dist: pyside6 (>=6.8.1,<7.0.0)
|
14
16
|
Description-Content-Type: text/markdown
|
15
17
|
|
@@ -85,7 +87,7 @@ This project uses PySide6, which is licensed under the LGPL (Lesser General Publ
|
|
85
87
|
|
86
88
|
## Contributing 🤝
|
87
89
|
|
88
|
-
|
90
|
+
Not Yet
|
89
91
|
|
90
92
|
## Issues
|
91
93
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "pyloid"
|
3
|
-
version = "0.
|
3
|
+
version = "0.20.2-dev2"
|
4
4
|
description = ""
|
5
5
|
authors = ["aesthetics-of-record <111675679+aesthetics-of-record@users.noreply.github.com>"]
|
6
6
|
readme = "README.md"
|
@@ -11,6 +11,7 @@ packages = [
|
|
11
11
|
[tool.poetry.dependencies]
|
12
12
|
python = ">=3.9,<3.14"
|
13
13
|
pyside6 = "^6.8.1"
|
14
|
+
pyinstaller = "^6.11.1"
|
14
15
|
|
15
16
|
|
16
17
|
[build-system]
|
@@ -0,0 +1,8 @@
|
|
1
|
+
from .pyloid import Pyloid
|
2
|
+
from .api import PyloidAPI, Bridge
|
3
|
+
from .utils import get_production_path, is_production
|
4
|
+
from .tray import TrayEvent
|
5
|
+
from .timer import PyloidTimer
|
6
|
+
from .builder import build_from_spec, cleanup_before_build, create_spec_from_json, get_site_packages
|
7
|
+
|
8
|
+
__all__ = ['Pyloid', 'PyloidAPI', 'Bridge', 'get_production_path', 'is_production', 'TrayEvent', 'PyloidTimer', 'build_from_spec', 'cleanup_before_build', 'create_spec_from_json', 'get_site_packages']
|
@@ -11,7 +11,11 @@ from PySide6.QtGui import (
|
|
11
11
|
QCursor,
|
12
12
|
)
|
13
13
|
from PySide6.QtCore import Qt, QPoint, QUrl, QEvent, QFile
|
14
|
-
from PySide6.QtWebEngineCore import
|
14
|
+
from PySide6.QtWebEngineCore import (
|
15
|
+
QWebEnginePage,
|
16
|
+
QWebEngineSettings,
|
17
|
+
QWebEngineUrlRequestInterceptor,
|
18
|
+
)
|
15
19
|
from .api import PyloidAPI
|
16
20
|
import uuid
|
17
21
|
from typing import List, Optional, Dict, Callable
|
@@ -40,10 +44,15 @@ class CustomWebPage(QWebEnginePage):
|
|
40
44
|
self.desktopMediaRequested.connect(self._handleDesktopMediaRequest)
|
41
45
|
self._permission_handlers = {}
|
42
46
|
self._desktop_media_handler = None
|
47
|
+
self._url_handlers = {} # URL 핸들러 저장을 위한 딕셔너리 추가
|
48
|
+
|
49
|
+
# interceptor ( all url request )
|
50
|
+
self.interceptor = CustomUrlInterceptor()
|
51
|
+
self.profile().setUrlRequestInterceptor(self.interceptor)
|
43
52
|
|
44
53
|
def _handlePermissionRequest(self, origin: QUrl, feature: QWebEnginePage.Feature):
|
45
54
|
print(origin, feature)
|
46
|
-
|
55
|
+
|
47
56
|
"""Default permission request handler"""
|
48
57
|
if feature in self._permission_handlers:
|
49
58
|
# Execute if a handler is registered
|
@@ -62,9 +71,41 @@ class CustomWebPage(QWebEnginePage):
|
|
62
71
|
def _handleDesktopMediaRequest(self, *args, **kwargs):
|
63
72
|
print("Desktop media request received:", args, kwargs)
|
64
73
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
74
|
+
# interceptor ( navigation request )
|
75
|
+
def acceptNavigationRequest(self, url, navigation_type, is_main_frame):
|
76
|
+
"""네비게이션 요청을 처리하는 메서드"""
|
77
|
+
print(f"Navigation Request - URL: {url.toString()}")
|
78
|
+
print(f"Navigation Type: {navigation_type}")
|
79
|
+
print(f"Is Main Frame: {is_main_frame}")
|
80
|
+
|
81
|
+
# # URL이 구글이 아닐 경우에만 구글로 리다이렉트
|
82
|
+
# if "example.com" not in url.toString():
|
83
|
+
# self.setUrl(QUrl("https://www.example.com"))
|
84
|
+
# return False
|
85
|
+
|
86
|
+
return True
|
87
|
+
|
88
|
+
def add_url_handler(self, pattern: str, handler):
|
89
|
+
"""URL 패턴에 대한 핸들러 등록
|
90
|
+
|
91
|
+
Parameters:
|
92
|
+
-----------
|
93
|
+
pattern : str
|
94
|
+
정규표현식 패턴
|
95
|
+
handler : callable
|
96
|
+
URL을 인자로 받고 bool을 반환해야 함
|
97
|
+
True를 반환하면 네비게이션을 허용, False를 반환하면 차단
|
98
|
+
"""
|
99
|
+
import re
|
100
|
+
|
101
|
+
self._url_handlers[re.compile(pattern)] = handler
|
102
|
+
|
103
|
+
|
104
|
+
# interceptor ( all url request )
|
105
|
+
class CustomUrlInterceptor(QWebEngineUrlRequestInterceptor):
|
106
|
+
def interceptRequest(self, info):
|
107
|
+
url = info.requestUrl().toString()
|
108
|
+
print(url)
|
68
109
|
|
69
110
|
|
70
111
|
class CustomWebEngineView(QWebEngineView):
|
@@ -81,15 +122,15 @@ class CustomWebEngineView(QWebEngineView):
|
|
81
122
|
self.resize_direction = None
|
82
123
|
self.screen_geometry = self.screen().virtualGeometry()
|
83
124
|
self.is_resizing_enabled = True
|
84
|
-
|
125
|
+
|
85
126
|
self.setAttribute(Qt.WA_SetCursor, False)
|
86
|
-
|
127
|
+
|
87
128
|
self.is_in_resize_area = False
|
88
129
|
|
89
130
|
def mouse_press_event(self, event):
|
90
131
|
if self.parent.frame or not self.is_resizing_enabled:
|
91
132
|
return
|
92
|
-
|
133
|
+
|
93
134
|
if event.button() == Qt.LeftButton:
|
94
135
|
self.resize_direction = self.get_resize_direction(event.pos())
|
95
136
|
if self.resize_direction:
|
@@ -104,19 +145,19 @@ class CustomWebEngineView(QWebEngineView):
|
|
104
145
|
def mouse_move_event(self, event):
|
105
146
|
if self.parent.frame or not self.is_resizing_enabled:
|
106
147
|
return
|
107
|
-
|
148
|
+
|
108
149
|
# Check resize direction
|
109
150
|
was_in_resize_area = self.is_in_resize_area
|
110
151
|
resize_direction = self.get_resize_direction(event.pos())
|
111
152
|
self.is_in_resize_area = bool(resize_direction)
|
112
|
-
|
153
|
+
|
113
154
|
if resize_direction and not self.is_resizing:
|
114
155
|
self.set_cursor_for_resize_direction(resize_direction)
|
115
|
-
|
156
|
+
|
116
157
|
if self.is_resizing:
|
117
158
|
self.resize_window(event.globalPos())
|
118
159
|
return
|
119
|
-
|
160
|
+
|
120
161
|
# Change cursor when entering/leaving resize area
|
121
162
|
if self.is_in_resize_area != was_in_resize_area:
|
122
163
|
if self.is_in_resize_area:
|
@@ -129,12 +170,12 @@ class CustomWebEngineView(QWebEngineView):
|
|
129
170
|
def mouse_release_event(self, event):
|
130
171
|
if self.parent.frame or not self.is_resizing_enabled:
|
131
172
|
return
|
132
|
-
|
173
|
+
|
133
174
|
if event.button() == Qt.LeftButton:
|
134
175
|
self.is_resizing = False
|
135
|
-
|
176
|
+
|
136
177
|
if self.resize_direction:
|
137
|
-
self.unsetCursor()
|
178
|
+
self.unsetCursor()
|
138
179
|
self.resize_direction = None
|
139
180
|
|
140
181
|
self.setAttribute(Qt.WA_SetCursor, False)
|
@@ -181,7 +222,7 @@ class CustomWebEngineView(QWebEngineView):
|
|
181
222
|
cursor = Qt.SizeFDiagCursor
|
182
223
|
elif direction in ["right-top", "left-bottom"]:
|
183
224
|
cursor = Qt.SizeBDiagCursor
|
184
|
-
|
225
|
+
|
185
226
|
if cursor:
|
186
227
|
self.setCursor(cursor)
|
187
228
|
self.setAttribute(Qt.WA_SetCursor, True)
|
@@ -378,7 +419,7 @@ class BrowserWindow:
|
|
378
419
|
js_api.window_id = self.id
|
379
420
|
js_api.window = self
|
380
421
|
js_api.app = self.app
|
381
|
-
|
422
|
+
|
382
423
|
self.channel.registerObject(js_api.__class__.__name__, js_api)
|
383
424
|
|
384
425
|
self.web_view.page().setWebChannel(self.channel)
|
@@ -1887,12 +1928,12 @@ class BrowserWindow:
|
|
1887
1928
|
def set_desktop_media_handler(self, handler):
|
1888
1929
|
"""
|
1889
1930
|
데스크톱 미디어(화면/윈도우) 선택 핸들러를 설정합니다.
|
1890
|
-
|
1931
|
+
|
1891
1932
|
Parameters
|
1892
1933
|
----------
|
1893
1934
|
handler : callable
|
1894
|
-
요청을 처리할 핸들러 함수. QWebEngineDesktopMediaRequest
|
1895
|
-
|
1935
|
+
요청을 처리할 핸들러 함수. QWebEngineDesktopMediaRequest 인자로 받습니다.
|
1936
|
+
|
1896
1937
|
Examples
|
1897
1938
|
--------
|
1898
1939
|
```python
|
@@ -1900,15 +1941,15 @@ class BrowserWindow:
|
|
1900
1941
|
# 사용 가능한 화면 목록 출력
|
1901
1942
|
for screen in request.screenList():
|
1902
1943
|
print(f"Screen: {screen.name}")
|
1903
|
-
|
1944
|
+
|
1904
1945
|
# 사용 가능한 윈도우 목록 출력
|
1905
1946
|
for window in request.windowList():
|
1906
1947
|
print(f"Window: {window.name}")
|
1907
|
-
|
1948
|
+
|
1908
1949
|
# 첫 번째 화면 선택
|
1909
1950
|
if request.screenList():
|
1910
1951
|
request.selectScreen(request.screenList()[0])
|
1911
|
-
|
1952
|
+
|
1912
1953
|
window.set_desktop_media_handler(custom_media_handler)
|
1913
1954
|
```
|
1914
1955
|
"""
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import json
|
2
|
+
from pathlib import Path
|
3
|
+
from PyInstaller.__main__ import run as pyinstaller_run
|
4
|
+
import shutil
|
5
|
+
import site
|
6
|
+
from pyloid.builder.spec import create_spec_from_json
|
7
|
+
|
8
|
+
__all__ = ['create_spec_from_json', 'cleanup_before_build', 'build_from_spec', 'get_site_packages']
|
9
|
+
|
10
|
+
def cleanup_before_build(json_path):
|
11
|
+
"""Function to clean up unnecessary files before build"""
|
12
|
+
try:
|
13
|
+
with open(json_path, 'r', encoding='utf-8') as f:
|
14
|
+
config = json.load(f)
|
15
|
+
|
16
|
+
cleanup_patterns = config.get('before_build', {}).get('cleanup_patterns', [])
|
17
|
+
if not cleanup_patterns:
|
18
|
+
return Exception("Cannot find cleanup patterns.")
|
19
|
+
|
20
|
+
site_packages = get_site_packages()
|
21
|
+
if not site_packages:
|
22
|
+
raise Exception("Cannot find site-packages directory.")
|
23
|
+
|
24
|
+
dist_dir = Path(f'{site_packages}')
|
25
|
+
if not dist_dir.exists():
|
26
|
+
raise Exception(f"Cannot find directory to clean: {dist_dir}")
|
27
|
+
|
28
|
+
print("\033[1;34mCleaning up unnecessary files...\033[0m")
|
29
|
+
exclude_patterns = [p[1:] for p in cleanup_patterns if p.startswith('!')]
|
30
|
+
include_patterns = [p for p in cleanup_patterns if not p.startswith('!')]
|
31
|
+
|
32
|
+
for pattern in include_patterns:
|
33
|
+
matching_files = list(dist_dir.glob(pattern))
|
34
|
+
for file_path in matching_files:
|
35
|
+
if any(file_path.match(p) for p in exclude_patterns):
|
36
|
+
print(f"\033[33mSkipping: {file_path}\033[0m")
|
37
|
+
continue
|
38
|
+
print(f"\033[33mRemoving: {file_path}\033[0m")
|
39
|
+
if file_path.is_dir():
|
40
|
+
shutil.rmtree(file_path)
|
41
|
+
else:
|
42
|
+
file_path.unlink()
|
43
|
+
print(f"\033[32mRemoved: {file_path}\033[0m")
|
44
|
+
|
45
|
+
print("\033[1;32mFile cleanup completed.\033[0m")
|
46
|
+
|
47
|
+
except Exception as e:
|
48
|
+
raise Exception(f"\033[1;31mError occurred during file cleanup: {e}\033[0m")
|
49
|
+
|
50
|
+
def build_from_spec(spec_path):
|
51
|
+
try:
|
52
|
+
pyinstaller_run([
|
53
|
+
'--clean', # Clean temporary files
|
54
|
+
spec_path # Spec file path
|
55
|
+
])
|
56
|
+
print("Build completed.")
|
57
|
+
|
58
|
+
except Exception as e:
|
59
|
+
raise Exception(f"Error occurred during build: {e}")
|
60
|
+
|
61
|
+
def get_site_packages():
|
62
|
+
"""
|
63
|
+
Returns the path to the site-packages directory.
|
64
|
+
Raises an exception if the directory is not found.
|
65
|
+
"""
|
66
|
+
for path in site.getsitepackages():
|
67
|
+
if 'site-packages' in path:
|
68
|
+
return path
|
69
|
+
raise Exception("Site-packages directory not found.")
|
70
|
+
|
71
|
+
def main():
|
72
|
+
spec_path = create_spec_from_json('build_config.json')
|
73
|
+
|
74
|
+
cleanup_before_build('build_config.json')
|
75
|
+
|
76
|
+
# build_from_spec(spec_path)
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
if __name__ == "__main__":
|
81
|
+
main()
|
@@ -0,0 +1,73 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
+
"type": "object",
|
4
|
+
"required": ["before_build", "name", "main_script", "datas", "excludes", "icon", "bundle"],
|
5
|
+
"properties": {
|
6
|
+
"before_build": {
|
7
|
+
"type": "object",
|
8
|
+
"properties": {
|
9
|
+
"cleanup_patterns": {
|
10
|
+
"type": "array",
|
11
|
+
"items": {
|
12
|
+
"type": "string"
|
13
|
+
},
|
14
|
+
"description": "List of file patterns to clean up in the library folder before building (used to remove files that cannot be excluded through pyinstaller) - default path is site-packages"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
},
|
18
|
+
"name": {
|
19
|
+
"type": "string",
|
20
|
+
"description": "Application name"
|
21
|
+
},
|
22
|
+
"main_script": {
|
23
|
+
"type": "string",
|
24
|
+
"description": "Path to the main Python script"
|
25
|
+
},
|
26
|
+
"datas": {
|
27
|
+
"type": "array",
|
28
|
+
"items": {
|
29
|
+
"type": "array",
|
30
|
+
"minItems": 2,
|
31
|
+
"maxItems": 2,
|
32
|
+
"items": [
|
33
|
+
{ "type": "string" },
|
34
|
+
{ "type": "string" }
|
35
|
+
]
|
36
|
+
},
|
37
|
+
"description": "List of data files to include [source path, destination path]"
|
38
|
+
},
|
39
|
+
"excludes": {
|
40
|
+
"type": "array",
|
41
|
+
"items": {
|
42
|
+
"type": "string"
|
43
|
+
},
|
44
|
+
"description": "List of modules to exclude during build with pyinstaller"
|
45
|
+
},
|
46
|
+
"icon": {
|
47
|
+
"type": "string",
|
48
|
+
"description": "Path to the application icon file"
|
49
|
+
},
|
50
|
+
"bundle": {
|
51
|
+
"type": "object",
|
52
|
+
"properties": {
|
53
|
+
"windows": {
|
54
|
+
"type": "string",
|
55
|
+
"enum": ["onefile", "directory"],
|
56
|
+
"description": "Windows build type"
|
57
|
+
},
|
58
|
+
"macos": {
|
59
|
+
"type": "string",
|
60
|
+
"enum": ["app"],
|
61
|
+
"description": "macOS build type"
|
62
|
+
},
|
63
|
+
"linux": {
|
64
|
+
"type": "string",
|
65
|
+
"enum": ["onefile", "directory"],
|
66
|
+
"description": "Linux build type"
|
67
|
+
}
|
68
|
+
},
|
69
|
+
"required": ["windows", "macos", "linux"],
|
70
|
+
"description": "Build settings for each OS"
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
@@ -0,0 +1,266 @@
|
|
1
|
+
from pyloid.utils import get_platform
|
2
|
+
import json
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
def create_spec_from_json(json_path):
|
7
|
+
with open(json_path, "r", encoding="utf-8") as f:
|
8
|
+
config = json.load(f)
|
9
|
+
|
10
|
+
os_type = get_platform()
|
11
|
+
|
12
|
+
if os_type == "macos":
|
13
|
+
spec_content = _create_macos_spec(config)
|
14
|
+
elif os_type == "linux":
|
15
|
+
spec_content = _create_linux_spec(config)
|
16
|
+
else: # windows
|
17
|
+
spec_content = _create_windows_spec(config)
|
18
|
+
|
19
|
+
spec_path = Path(f"build-{os_type}.spec")
|
20
|
+
spec_path.write_text(spec_content, encoding="utf-8")
|
21
|
+
|
22
|
+
return str(spec_path)
|
23
|
+
|
24
|
+
|
25
|
+
def _create_windows_spec(config):
|
26
|
+
bundle_type = config.get("bundle", {}).get("windows", "directory")
|
27
|
+
console = config.get("console", False)
|
28
|
+
|
29
|
+
base_spec = f"""# -*- mode: python ; coding: utf-8 -*-
|
30
|
+
|
31
|
+
block_cipher = None
|
32
|
+
|
33
|
+
a = Analysis(
|
34
|
+
['{config['main_script']}'],
|
35
|
+
pathex={config.get('pathex', [])},
|
36
|
+
binaries={config.get('binaries', [])},
|
37
|
+
datas={config.get('datas', [])},
|
38
|
+
hiddenimports={config.get('hiddenimports', [])},
|
39
|
+
hookspath={config.get('hookspath', [])},
|
40
|
+
hooksconfig={config.get('hooksconfig', {})},
|
41
|
+
runtime_hooks={config.get('runtime_hooks', [])},
|
42
|
+
excludes={config.get('excludes', [])},
|
43
|
+
win_no_prefer_redirects=False,
|
44
|
+
win_private_assemblies=False,
|
45
|
+
cipher=block_cipher,
|
46
|
+
noarchive=False
|
47
|
+
)
|
48
|
+
|
49
|
+
pyz = PYZ(a.pure, a.zipped_data,
|
50
|
+
cipher=block_cipher)
|
51
|
+
"""
|
52
|
+
|
53
|
+
if bundle_type == "onefile":
|
54
|
+
return (
|
55
|
+
base_spec
|
56
|
+
+ f"""
|
57
|
+
exe = EXE(
|
58
|
+
pyz,
|
59
|
+
a.scripts,
|
60
|
+
a.binaries,
|
61
|
+
a.zipfiles,
|
62
|
+
a.datas,
|
63
|
+
[],
|
64
|
+
name='{config.get("name", "pyloid-app")}',
|
65
|
+
debug=False,
|
66
|
+
bootloader_ignore_signals=False,
|
67
|
+
strip=False,
|
68
|
+
upx=True,
|
69
|
+
upx_exclude=[],
|
70
|
+
runtime_tmpdir=None,
|
71
|
+
console={console},
|
72
|
+
disable_windowed_traceback=False,
|
73
|
+
argv_emulation=False,
|
74
|
+
target_arch=None,
|
75
|
+
codesign_identity=None,
|
76
|
+
entitlements_file=None,
|
77
|
+
icon='{config.get('icon', 'src-pyloid/icons/icon.ico')}'
|
78
|
+
)
|
79
|
+
"""
|
80
|
+
)
|
81
|
+
else:
|
82
|
+
return (
|
83
|
+
base_spec
|
84
|
+
+ f"""
|
85
|
+
exe = EXE(
|
86
|
+
pyz,
|
87
|
+
a.scripts,
|
88
|
+
[],
|
89
|
+
exclude_binaries=True,
|
90
|
+
name='{config.get("name", "pyloid-app")}',
|
91
|
+
debug=False,
|
92
|
+
bootloader_ignore_signals=False,
|
93
|
+
strip=False,
|
94
|
+
upx=True,
|
95
|
+
console={console},
|
96
|
+
disable_windowed_traceback=False,
|
97
|
+
argv_emulation=False,
|
98
|
+
target_arch=None,
|
99
|
+
codesign_identity=None,
|
100
|
+
entitlements_file=None,
|
101
|
+
icon='{config.get('icon', 'src-pyloid/icons/icon.ico')}'
|
102
|
+
)
|
103
|
+
|
104
|
+
coll = COLLECT(
|
105
|
+
exe,
|
106
|
+
a.binaries,
|
107
|
+
a.zipfiles,
|
108
|
+
a.datas,
|
109
|
+
strip=False,
|
110
|
+
upx=True,
|
111
|
+
upx_exclude=[],
|
112
|
+
name='{config.get("name", "pyloid-app")}'
|
113
|
+
)
|
114
|
+
"""
|
115
|
+
)
|
116
|
+
|
117
|
+
|
118
|
+
def _create_macos_spec(config):
|
119
|
+
console = config.get("console", False)
|
120
|
+
return f"""# -*- mode: python ; coding: utf-8 -*-
|
121
|
+
|
122
|
+
a = Analysis(
|
123
|
+
['{config['main_script']}'],
|
124
|
+
pathex={config.get('pathex', [])},
|
125
|
+
binaries={config.get('binaries', [])},
|
126
|
+
datas={config.get('datas', [])},
|
127
|
+
hiddenimports={config.get('hiddenimports', [])},
|
128
|
+
hookspath={config.get('hookspath', [])},
|
129
|
+
hooksconfig={config.get('hooksconfig', {})},
|
130
|
+
runtime_hooks={config.get('runtime_hooks', [])},
|
131
|
+
excludes={config.get('excludes', [])},
|
132
|
+
noarchive=False,
|
133
|
+
optimize=0
|
134
|
+
)
|
135
|
+
|
136
|
+
pyz = PYZ(a.pure)
|
137
|
+
|
138
|
+
exe = EXE(
|
139
|
+
pyz,
|
140
|
+
a.scripts,
|
141
|
+
[],
|
142
|
+
exclude_binaries=True,
|
143
|
+
name='{config.get("name", "pyloid-app")}',
|
144
|
+
debug=False,
|
145
|
+
bootloader_ignore_signals=False,
|
146
|
+
strip=False,
|
147
|
+
upx=True,
|
148
|
+
console={console},
|
149
|
+
disable_windowed_traceback=False,
|
150
|
+
argv_emulation=False,
|
151
|
+
target_arch=None,
|
152
|
+
codesign_identity=None,
|
153
|
+
entitlements_file=None,
|
154
|
+
icon='{config.get('icon', 'src-pyloid/icons/icon.ico')}'
|
155
|
+
)
|
156
|
+
|
157
|
+
coll = COLLECT(
|
158
|
+
exe,
|
159
|
+
a.binaries,
|
160
|
+
a.datas,
|
161
|
+
strip=False,
|
162
|
+
upx=True,
|
163
|
+
upx_exclude=[],
|
164
|
+
name='{config.get("name", "pyloid-app")}'
|
165
|
+
)
|
166
|
+
|
167
|
+
app = BUNDLE(
|
168
|
+
coll,
|
169
|
+
name='{config.get("name", "pyloid-app")}.app',
|
170
|
+
icon='{config.get('icon', 'src-pyloid/icons/icon.icns')}',
|
171
|
+
bundle_identifier=None
|
172
|
+
)
|
173
|
+
"""
|
174
|
+
|
175
|
+
|
176
|
+
def _create_linux_spec(config):
|
177
|
+
bundle_type = config.get("bundle", {}).get("linux", "directory")
|
178
|
+
console = config.get("console", False)
|
179
|
+
|
180
|
+
base_spec = f"""# -*- mode: python ; coding: utf-8 -*-
|
181
|
+
|
182
|
+
block_cipher = None
|
183
|
+
|
184
|
+
a = Analysis(
|
185
|
+
['{config['main_script']}'],
|
186
|
+
pathex={config.get('pathex', [])},
|
187
|
+
binaries={config.get('binaries', [])},
|
188
|
+
datas={config.get('datas', [])},
|
189
|
+
hiddenimports={config.get('hiddenimports', [])},
|
190
|
+
hookspath={config.get('hookspath', [])},
|
191
|
+
hooksconfig={config.get('hooksconfig', {})},
|
192
|
+
runtime_hooks={config.get('runtime_hooks', [])},
|
193
|
+
excludes={config.get('excludes', [])},
|
194
|
+
win_no_prefer_redirects=False,
|
195
|
+
win_private_assemblies=False,
|
196
|
+
cipher=block_cipher,
|
197
|
+
noarchive=False
|
198
|
+
)
|
199
|
+
|
200
|
+
pyz = PYZ(a.pure, a.zipped_data,
|
201
|
+
cipher=block_cipher)
|
202
|
+
"""
|
203
|
+
|
204
|
+
if bundle_type == "onefile":
|
205
|
+
return (
|
206
|
+
base_spec
|
207
|
+
+ f"""
|
208
|
+
exe = EXE(
|
209
|
+
pyz,
|
210
|
+
a.scripts,
|
211
|
+
a.binaries,
|
212
|
+
a.zipfiles,
|
213
|
+
a.datas,
|
214
|
+
[],
|
215
|
+
name='{config.get("name", "pyloid-app")}',
|
216
|
+
debug=False,
|
217
|
+
bootloader_ignore_signals=False,
|
218
|
+
strip=False,
|
219
|
+
upx=True,
|
220
|
+
upx_exclude=[],
|
221
|
+
runtime_tmpdir=None,
|
222
|
+
console={console},
|
223
|
+
disable_windowed_traceback=False,
|
224
|
+
argv_emulation=False,
|
225
|
+
target_arch=None,
|
226
|
+
codesign_identity=None,
|
227
|
+
entitlements_file=None,
|
228
|
+
icon='{config.get('icon', 'src-pyloid/icons/icon.png')}'
|
229
|
+
)
|
230
|
+
"""
|
231
|
+
)
|
232
|
+
else:
|
233
|
+
return (
|
234
|
+
base_spec
|
235
|
+
+ f"""
|
236
|
+
exe = EXE(
|
237
|
+
pyz,
|
238
|
+
a.scripts,
|
239
|
+
[],
|
240
|
+
exclude_binaries=True,
|
241
|
+
name='{config.get("name", "pyloid-app")}',
|
242
|
+
debug=False,
|
243
|
+
bootloader_ignore_signals=False,
|
244
|
+
strip=False,
|
245
|
+
upx=True,
|
246
|
+
console={console},
|
247
|
+
disable_windowed_traceback=False,
|
248
|
+
argv_emulation=False,
|
249
|
+
target_arch=None,
|
250
|
+
codesign_identity=None,
|
251
|
+
entitlements_file=None,
|
252
|
+
icon='{config.get('icon', 'src-pyloid/icons/icon.png')}'
|
253
|
+
)
|
254
|
+
|
255
|
+
coll = COLLECT(
|
256
|
+
exe,
|
257
|
+
a.binaries,
|
258
|
+
a.zipfiles,
|
259
|
+
a.datas,
|
260
|
+
strip=False,
|
261
|
+
upx=True,
|
262
|
+
upx_exclude=[],
|
263
|
+
name='{config.get("name", "pyloid-app")}'
|
264
|
+
)
|
265
|
+
"""
|
266
|
+
)
|
@@ -25,17 +25,20 @@ def get_production_path(path: Optional[str] = None) -> Optional[str]:
|
|
25
25
|
>>> print("Not in a production environment.")
|
26
26
|
"""
|
27
27
|
if is_production():
|
28
|
-
# Nuitka
|
29
28
|
if hasattr(sys, '_MEIPASS'):
|
30
29
|
# PyInstaller
|
31
30
|
base_path = sys._MEIPASS
|
32
31
|
else:
|
33
|
-
# Nuitka
|
34
|
-
base_path = os.path.dirname(
|
32
|
+
# Nuitka
|
33
|
+
base_path = os.path.dirname(sys.executable)
|
34
|
+
|
35
|
+
if base_path is None:
|
36
|
+
# 환경변수가 없는 경우 실행 파일 디렉토리 사용
|
37
|
+
base_path = os.path.dirname(os.path.abspath(sys.argv[0]))
|
38
|
+
|
35
39
|
|
36
40
|
return os.path.join(base_path, path) if path else base_path
|
37
41
|
else:
|
38
|
-
# 일반 Python 스크립트로 실행 중일 때
|
39
42
|
return None
|
40
43
|
|
41
44
|
|
@@ -71,19 +74,28 @@ def get_platform() -> str:
|
|
71
74
|
|
72
75
|
Returns
|
73
76
|
-------
|
74
|
-
"
|
75
|
-
- "
|
76
|
-
- "
|
77
|
-
- "
|
77
|
+
"windows" | "macos" | "linux"
|
78
|
+
- "windows" for Windows systems
|
79
|
+
- "macos" for macOS systems
|
80
|
+
- "linux" for Linux systems
|
78
81
|
|
79
82
|
Examples
|
80
83
|
--------
|
81
84
|
>>> from pyloid.utils import get_platform
|
82
85
|
>>> platform_name = get_platform()
|
83
86
|
>>> print(platform_name)
|
84
|
-
|
87
|
+
windows
|
85
88
|
"""
|
86
|
-
|
89
|
+
os_name = platform.system().lower()
|
90
|
+
os_type = {
|
91
|
+
'darwin': 'macos',
|
92
|
+
'linux': 'linux',
|
93
|
+
'windows': 'windows'
|
94
|
+
}.get(os_name)
|
95
|
+
if os_type is None:
|
96
|
+
raise ValueError(f"Unsupported platform: {os_name}")
|
97
|
+
|
98
|
+
return os_type
|
87
99
|
|
88
100
|
def get_absolute_path(path: str) -> str:
|
89
101
|
"""
|
@@ -1,7 +0,0 @@
|
|
1
|
-
from .pyloid import Pyloid
|
2
|
-
from .api import PyloidAPI, Bridge
|
3
|
-
from .utils import get_production_path, is_production
|
4
|
-
from .tray import TrayEvent
|
5
|
-
from .timer import PyloidTimer
|
6
|
-
|
7
|
-
__all__ = ['Pyloid', 'PyloidAPI', 'Bridge', 'get_production_path', 'is_production', 'TrayEvent', 'PyloidTimer']
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|