pyloid 0.18.2__tar.gz → 0.20.2.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {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
|