-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
304 lines (247 loc) · 11.1 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
import os
import sys
import json
import time
from pathlib import Path
import numpy as np
from PyQt6.QtCore import QObject, pyqtSlot, QUrl, QSize, Qt, QTimer
from PyQt6.QtWidgets import QApplication, QMainWindow, QMessageBox, QProgressDialog, QLabel
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtGui import QIcon
import types
import importlib
import atexit
class DynamicPyBridge(QObject):
"""Dynamic Python handler that loads functions from a JSON configuration file"""
def __init__(self, config_path, parent=None):
super().__init__(parent)
self.main_window = parent
self.functions = {}
self.config_path = config_path
self.handler = None
self.json_config = None
def load_config_with_progress(self, progress_dialog):
"""Load the bridge functions from the JSON configuration file with progress updates"""
try:
# First, load the JSON configuration
with open(self.config_path, 'r') as f:
self.json_config = json.load(f)
functions = self.json_config.get('functions', [])
total_functions = len(functions)
if total_functions == 0:
progress_dialog.setValue(100)
return
# Store the functions for reference
for i, func in enumerate(functions):
# Update progress dialog
name = func['name']
progress_dialog.setLabelText(f"Loading function: {name}")
progress_percent = int((i / total_functions) * 50) # First half of progress
progress_dialog.setValue(progress_percent)
# Store the function code
code = func.get('code', 'return None')
self.functions[name] = code
# Add a small delay to make the progress visible
QApplication.processEvents()
time.sleep(0.1) # Simulate loading time
# Now generate the temp.py file with the PyHandler class
progress_dialog.setLabelText("Generating PyHandler class...")
progress_dialog.setValue(60)
QApplication.processEvents()
self._generate_temp_py_file()
# Import or reload the temp module
progress_dialog.setLabelText("Loading PyHandler module...")
progress_dialog.setValue(80)
QApplication.processEvents()
if "temp" in sys.modules:
importlib.reload(sys.modules["temp"])
else:
import temp
# Create an instance of the PyHandler class
progress_dialog.setLabelText("Creating PyHandler instance...")
progress_dialog.setValue(90)
QApplication.processEvents()
self.handler = sys.modules["temp"].create_handler(self.main_window, self)
# Set to 100% when done
progress_dialog.setValue(100)
print(f"Loaded {len(self.functions)} functions from configuration")
except Exception as e:
print(f"Error loading configuration: {e}")
progress_dialog.cancel()
def _generate_temp_py_file(self):
"""Generate the temp.py file with the PyHandler class based on the JSON configuration"""
if not self.json_config:
print("No JSON configuration loaded")
return
# Start building the PyHandler class
py_handler_code = """from PyQt6.QtCore import QObject, pyqtSlot
import importlib
import sys
import numpy as np
from PyQt6.QtWidgets import QApplication, QMessageBox
class PyHandler(QObject):
\"\"\"
A QObject-derived class that exposes Python methods to JavaScript via QWebChannel.
Methods are dynamically added based on the JSON configuration.
\"\"\"
def __init__(self, main_window=None, parent=None):
super().__init__(parent)
self.main_window = main_window
"""
# Add each function from the JSON to the PyHandler class
for func in self.json_config.get('functions', []):
name = func['name']
code = func.get('code', 'return None')
parameters = func.get('parameters', [])
return_type = func.get('return_type', 'str')
# Create the method with the appropriate pyqtSlot decorator
# Ensure proper syntax for the pyqtSlot decorator
param_types = []
for _ in parameters:
param_types.append('str') # Default to str for all parameters
# Create the decorator with proper syntax
if param_types:
decorator = f"@pyqtSlot({', '.join(param_types)}, result={return_type})"
else:
decorator = f"@pyqtSlot(result={return_type})"
# Debug: Print the function details
print(f"Generating method: {name}, Parameters: {parameters}, Return type: {return_type}")
print(f"Decorator: {decorator}")
# Ensure the code is properly indented and doesn't contain syntax errors
# Split the code into lines and indent each line
code_lines = code.split(';')
indented_code = []
for line in code_lines:
line = line.strip()
if line:
indented_code.append(f" {line}")
# Join the lines with newlines for better readability
indented_code_str = '\n'.join(indented_code)
method_code = f"""
{decorator}
def {name}({', '.join(['self'] + parameters)}):
\"\"\"
{func.get('description', f'Implementation of {name}')}
\"\"\"
try:
{indented_code_str}
except Exception as e:
print(f"Error executing {name}: {{e}}")
return {'""' if return_type == 'str' else 'False' if return_type == 'bool' else 'None'}
"""
py_handler_code += method_code
# Add the create_handler function
py_handler_code += """
def create_handler(main_window=None, parent=None):
\"\"\"
Create and return an instance of the PyHandler class.
This function is called from main.py to get the handler instance.
\"\"\"
return PyHandler(main_window, parent)
"""
# Write the PyHandler class to temp.py
with open("temp.py", "w") as f:
f.write(py_handler_code)
# Debug: Print the generated file
print("Generated temp.py with PyHandler class")
try:
# Try to compile the generated code to check for syntax errors
with open("temp.py", "r") as f:
source = f.read()
compile(source, "temp.py", "exec")
print("Compilation successful: No syntax errors in generated code")
except SyntaxError as e:
print(f"Compilation failed: Syntax error in generated code: {e}")
# Print the problematic line
lines = source.split('\n')
if e.lineno <= len(lines):
print(f"Line {e.lineno}: {lines[e.lineno-1]}")
def get_handler(self):
"""Get the PyHandler instance"""
if self.handler is None:
# If handler is not created yet, we need to load the configuration first
print("Handler not initialized. Please load the configuration first.")
return None
return self.handler
class MainWindow(QMainWindow):
"""Main application window"""
def __init__(self):
super().__init__()
self.init_ui()
# Register cleanup function to be called on exit
atexit.register(self.cleanup)
def init_ui(self):
# Set window properties
self.setWindowTitle("Serverless Desktop Python v0.1")
self.setFixedSize(1280, 720)
# Center the window on the screen
screen_geometry = QApplication.primaryScreen().geometry()
x = (screen_geometry.width() - self.width()) // 2
y = (screen_geometry.height() - self.height()) // 2
self.move(x, y)
# Create the WebView
self.web_view = QWebEngineView()
self.setCentralWidget(self.web_view)
# Set up the Python-JavaScript bridge
self.channel = QWebChannel()
# Create the bridge with the config path
base_dir = Path(__file__).resolve().parent
config_path = base_dir / "appdata" / "bridge_config.json"
self.bridge = DynamicPyBridge(config_path, self)
# Show loading dialog and load the configuration
self.show_loading_dialog()
# Register the PyHandler instance directly with the channel
# This allows JavaScript to access the PyHandler methods directly
handler = self.bridge.get_handler()
if handler:
self.channel.registerObject("pythonHandler", handler)
else:
print("Warning: No handler available to register with QWebChannel")
self.web_view.page().setWebChannel(self.channel)
# Load the HTML file
html_path = base_dir / "appdata" / "index.html"
self.web_view.load(QUrl.fromLocalFile(str(html_path)))
# Connect signals
self.web_view.loadFinished.connect(self.on_load_finished)
def show_loading_dialog(self):
"""Show a loading dialog with a progress bar while loading the configuration"""
# Create a progress dialog
progress = QProgressDialog("Loading functions from configuration...", None, 0, 100, self)
progress.setWindowTitle("Loading Configuration")
progress.setWindowModality(Qt.WindowModality.ApplicationModal)
progress.setMinimumDuration(0) # Show immediately
progress.setAutoClose(True)
progress.setCancelButton(None) # No cancel button
progress.setMinimumWidth(400)
# Show the dialog
progress.show()
QApplication.processEvents()
# Load the configuration with progress updates
self.bridge.load_config_with_progress(progress)
# Close the dialog when done
progress.close()
def on_load_finished(self, ok):
if ok:
print("Page loaded successfully")
else:
print("Failed to load the page")
def cleanup(self):
"""Clean up resources before exiting"""
print("Cleaning up before exit...")
# Clear the temp.py file
try:
with open("temp.py", "w") as f:
f.write("# This file was cleared on application exit\n")
print("temp.py file cleared")
except Exception as e:
print(f"Error clearing temp.py: {e}")
def main():
app = QApplication(sys.argv)
# Create and show the main window
window = MainWindow()
window.show()
# Start the application event loop
sys.exit(app.exec())
if __name__ == "__main__":
main()