aegis-framework 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/aegis/__init__.py +19 -0
- package/aegis/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/builder/__init__.py +5 -0
- package/aegis/builder/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/builder/__pycache__/builder.cpython-312.pyc +0 -0
- package/aegis/builder/builder.py +301 -0
- package/aegis/cli/__init__.py +5 -0
- package/aegis/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/cli/__pycache__/cli.cpython-312.pyc +0 -0
- package/aegis/cli/cli.py +607 -0
- package/aegis/core/__init__.py +16 -0
- package/aegis/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/aegis/core/__pycache__/aegis.cpython-312.pyc +0 -0
- package/aegis/core/__pycache__/bridge.cpython-312.pyc +0 -0
- package/aegis/core/__pycache__/window.cpython-312.pyc +0 -0
- package/aegis/core/aegis.py +97 -0
- package/aegis/core/bridge.py +270 -0
- package/aegis/core/preload.py +160 -0
- package/aegis/core/window.py +603 -0
- package/aegis/runtime/__init__.py +1 -0
- package/aegis/runtime/aegis-api.js +519 -0
- package/aegis-cli.py +18 -0
- package/bin/aegis +81 -0
- package/package.json +51 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aegis Window Manager
|
|
3
|
+
Manages WebKit2GTK windows with full customization support
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import gi
|
|
7
|
+
gi.require_version('Gtk', '3.0')
|
|
8
|
+
gi.require_version('WebKit2', '4.1')
|
|
9
|
+
|
|
10
|
+
from gi.repository import Gtk, WebKit2, Gdk, GLib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AegisWindow(Gtk.Window):
|
|
16
|
+
"""
|
|
17
|
+
A WebKit2GTK-based window with Aegis bridge integration
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config=None):
|
|
21
|
+
super().__init__()
|
|
22
|
+
|
|
23
|
+
self.config = config or {}
|
|
24
|
+
self._setup_window()
|
|
25
|
+
self._setup_webview()
|
|
26
|
+
self._setup_bridge()
|
|
27
|
+
|
|
28
|
+
def _setup_window(self):
|
|
29
|
+
"""Configure the main window"""
|
|
30
|
+
# Window properties from config
|
|
31
|
+
title = self.config.get('title', 'Aegis App')
|
|
32
|
+
width = self.config.get('width', 1200)
|
|
33
|
+
height = self.config.get('height', 800)
|
|
34
|
+
resizable = self.config.get('resizable', True)
|
|
35
|
+
decorated = self.config.get('frame', True)
|
|
36
|
+
|
|
37
|
+
self.set_title(title)
|
|
38
|
+
self.set_default_size(width, height)
|
|
39
|
+
self.set_resizable(resizable)
|
|
40
|
+
self.set_decorated(decorated)
|
|
41
|
+
self.set_position(Gtk.WindowPosition.CENTER)
|
|
42
|
+
|
|
43
|
+
# Handle close
|
|
44
|
+
self.connect('destroy', Gtk.main_quit)
|
|
45
|
+
|
|
46
|
+
# Frameless window support
|
|
47
|
+
if not decorated:
|
|
48
|
+
self._setup_frameless()
|
|
49
|
+
|
|
50
|
+
def _setup_frameless(self):
|
|
51
|
+
"""Setup frameless window with drag support"""
|
|
52
|
+
self.set_app_paintable(True)
|
|
53
|
+
|
|
54
|
+
# Enable dragging from anywhere
|
|
55
|
+
self.drag_start_x = 0
|
|
56
|
+
self.drag_start_y = 0
|
|
57
|
+
|
|
58
|
+
def _setup_webview(self):
|
|
59
|
+
"""Setup WebKit2GTK webview"""
|
|
60
|
+
# Create webview with settings
|
|
61
|
+
self.webview = WebKit2.WebView()
|
|
62
|
+
settings = self.webview.get_settings()
|
|
63
|
+
|
|
64
|
+
# Enable developer tools
|
|
65
|
+
settings.set_enable_developer_extras(True)
|
|
66
|
+
settings.set_enable_javascript(True)
|
|
67
|
+
settings.set_javascript_can_access_clipboard(True)
|
|
68
|
+
settings.set_enable_write_console_messages_to_stdout(True)
|
|
69
|
+
|
|
70
|
+
# Allow file access
|
|
71
|
+
settings.set_allow_file_access_from_file_urls(True)
|
|
72
|
+
settings.set_allow_universal_access_from_file_urls(True)
|
|
73
|
+
|
|
74
|
+
# Hardware acceleration - disabled by default for compatibility
|
|
75
|
+
# nouveau and some other drivers have issues with GPU acceleration
|
|
76
|
+
settings.set_hardware_acceleration_policy(
|
|
77
|
+
WebKit2.HardwareAccelerationPolicy.NEVER
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Disable context menu if configured
|
|
81
|
+
if not self.config.get('contextMenu', True):
|
|
82
|
+
self.webview.connect('context-menu', lambda *args: True)
|
|
83
|
+
|
|
84
|
+
# Add to window
|
|
85
|
+
self.add(self.webview)
|
|
86
|
+
|
|
87
|
+
def _setup_bridge(self):
|
|
88
|
+
"""Setup JavaScript bridge for IPC"""
|
|
89
|
+
# Get user content manager
|
|
90
|
+
self.content_manager = self.webview.get_user_content_manager()
|
|
91
|
+
|
|
92
|
+
# Register message handler for Aegis calls
|
|
93
|
+
self.content_manager.register_script_message_handler('aegis')
|
|
94
|
+
self.content_manager.connect(
|
|
95
|
+
'script-message-received::aegis',
|
|
96
|
+
self._on_message_received
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Pending callbacks for async responses
|
|
100
|
+
self._pending_callbacks = {}
|
|
101
|
+
self._callback_id = 0
|
|
102
|
+
|
|
103
|
+
def _on_message_received(self, content_manager, js_result):
|
|
104
|
+
"""Handle messages from JavaScript"""
|
|
105
|
+
callback_id = None
|
|
106
|
+
try:
|
|
107
|
+
# WebKit2 4.1 uses get_js_value()
|
|
108
|
+
js_value = js_result.get_js_value()
|
|
109
|
+
data = json.loads(js_value.to_string())
|
|
110
|
+
|
|
111
|
+
action = data.get('action')
|
|
112
|
+
payload = data.get('payload', {})
|
|
113
|
+
callback_id = data.get('callbackId')
|
|
114
|
+
|
|
115
|
+
print(f"[Aegis] Action: {action}, Payload: {payload}")
|
|
116
|
+
|
|
117
|
+
# Process action and get result
|
|
118
|
+
result = self._process_action(action, payload)
|
|
119
|
+
|
|
120
|
+
# Send response back to JavaScript
|
|
121
|
+
if callback_id:
|
|
122
|
+
self._send_response(callback_id, result)
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"[Aegis Bridge] Error: {e}")
|
|
126
|
+
import traceback
|
|
127
|
+
traceback.print_exc()
|
|
128
|
+
if callback_id:
|
|
129
|
+
self._send_error(callback_id, str(e))
|
|
130
|
+
|
|
131
|
+
def _process_action(self, action, payload):
|
|
132
|
+
"""Process an Aegis action and return result"""
|
|
133
|
+
handlers = {
|
|
134
|
+
'read': self._handle_read,
|
|
135
|
+
'write': self._handle_write,
|
|
136
|
+
'run': self._handle_run,
|
|
137
|
+
'exists': self._handle_exists,
|
|
138
|
+
'mkdir': self._handle_mkdir,
|
|
139
|
+
'remove': self._handle_remove,
|
|
140
|
+
'copy': self._handle_copy,
|
|
141
|
+
'move': self._handle_move,
|
|
142
|
+
'dialog.open': self._handle_dialog_open,
|
|
143
|
+
'dialog.save': self._handle_dialog_save,
|
|
144
|
+
'dialog.message': self._handle_dialog_message,
|
|
145
|
+
'app.quit': self._handle_app_quit,
|
|
146
|
+
'app.minimize': self._handle_app_minimize,
|
|
147
|
+
'app.maximize': self._handle_app_maximize,
|
|
148
|
+
'app.getPath': self._handle_app_get_path,
|
|
149
|
+
'window.startDrag': self._handle_window_start_drag,
|
|
150
|
+
'window.resize': self._handle_window_resize,
|
|
151
|
+
'window.setSize': self._handle_window_set_size,
|
|
152
|
+
'window.getSize': self._handle_window_get_size,
|
|
153
|
+
'window.setPosition': self._handle_window_set_position,
|
|
154
|
+
'window.getPosition': self._handle_window_get_position,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
handler = handlers.get(action)
|
|
158
|
+
if handler:
|
|
159
|
+
return handler(payload)
|
|
160
|
+
else:
|
|
161
|
+
raise ValueError(f"Unknown action: {action}")
|
|
162
|
+
|
|
163
|
+
def _send_response(self, callback_id, result):
|
|
164
|
+
"""Send successful response to JavaScript"""
|
|
165
|
+
response = json.dumps({
|
|
166
|
+
'callbackId': callback_id,
|
|
167
|
+
'success': True,
|
|
168
|
+
'data': result
|
|
169
|
+
})
|
|
170
|
+
script = f"window.__aegisResolve({response})"
|
|
171
|
+
self.webview.evaluate_javascript(script, -1, None, None, None, None, None)
|
|
172
|
+
|
|
173
|
+
def _send_error(self, callback_id, error):
|
|
174
|
+
"""Send error response to JavaScript"""
|
|
175
|
+
response = json.dumps({
|
|
176
|
+
'callbackId': callback_id,
|
|
177
|
+
'success': False,
|
|
178
|
+
'error': error
|
|
179
|
+
})
|
|
180
|
+
script = f"window.__aegisResolve({response})"
|
|
181
|
+
self.webview.evaluate_javascript(script, -1, None, None, None, None, None)
|
|
182
|
+
|
|
183
|
+
# ==================== Action Handlers ====================
|
|
184
|
+
|
|
185
|
+
def _handle_read(self, payload):
|
|
186
|
+
"""Read file or directory contents"""
|
|
187
|
+
path = payload.get('path', '.')
|
|
188
|
+
file = payload.get('file')
|
|
189
|
+
|
|
190
|
+
if file:
|
|
191
|
+
# Read single file
|
|
192
|
+
full_path = os.path.join(path, file)
|
|
193
|
+
with open(full_path, 'r', encoding='utf-8') as f:
|
|
194
|
+
return {'content': f.read(), 'path': full_path}
|
|
195
|
+
else:
|
|
196
|
+
# List directory
|
|
197
|
+
entries = []
|
|
198
|
+
for entry in os.listdir(path):
|
|
199
|
+
full_path = os.path.join(path, entry)
|
|
200
|
+
try:
|
|
201
|
+
stat = os.stat(full_path)
|
|
202
|
+
entries.append({
|
|
203
|
+
'name': entry,
|
|
204
|
+
'isDirectory': os.path.isdir(full_path),
|
|
205
|
+
'isFile': os.path.isfile(full_path),
|
|
206
|
+
'size': stat.st_size if os.path.isfile(full_path) else 0,
|
|
207
|
+
'modified': stat.st_mtime
|
|
208
|
+
})
|
|
209
|
+
except (PermissionError, OSError):
|
|
210
|
+
# Skip files we can't access
|
|
211
|
+
entries.append({
|
|
212
|
+
'name': entry,
|
|
213
|
+
'isDirectory': False,
|
|
214
|
+
'isFile': True,
|
|
215
|
+
'size': 0,
|
|
216
|
+
'modified': 0
|
|
217
|
+
})
|
|
218
|
+
return {'entries': entries, 'path': path}
|
|
219
|
+
|
|
220
|
+
def _handle_write(self, payload):
|
|
221
|
+
"""Write content to file"""
|
|
222
|
+
path = payload.get('path', '.')
|
|
223
|
+
file = payload.get('file')
|
|
224
|
+
content = payload.get('content', '')
|
|
225
|
+
|
|
226
|
+
full_path = os.path.join(path, file)
|
|
227
|
+
|
|
228
|
+
# Create directories if needed
|
|
229
|
+
os.makedirs(os.path.dirname(full_path) or '.', exist_ok=True)
|
|
230
|
+
|
|
231
|
+
with open(full_path, 'w', encoding='utf-8') as f:
|
|
232
|
+
f.write(content)
|
|
233
|
+
|
|
234
|
+
return {'success': True, 'path': full_path}
|
|
235
|
+
|
|
236
|
+
def _handle_run(self, payload):
|
|
237
|
+
"""Execute Python or shell commands"""
|
|
238
|
+
import subprocess
|
|
239
|
+
|
|
240
|
+
if 'py' in payload:
|
|
241
|
+
# Execute Python code
|
|
242
|
+
try:
|
|
243
|
+
result = eval(payload['py'])
|
|
244
|
+
return {'output': str(result), 'exitCode': 0}
|
|
245
|
+
except:
|
|
246
|
+
exec_globals = {}
|
|
247
|
+
exec(payload['py'], exec_globals)
|
|
248
|
+
return {'output': '', 'exitCode': 0}
|
|
249
|
+
|
|
250
|
+
elif 'sh' in payload:
|
|
251
|
+
# Execute shell command
|
|
252
|
+
result = subprocess.run(
|
|
253
|
+
payload['sh'],
|
|
254
|
+
shell=True,
|
|
255
|
+
capture_output=True,
|
|
256
|
+
text=True
|
|
257
|
+
)
|
|
258
|
+
return {
|
|
259
|
+
'output': result.stdout,
|
|
260
|
+
'error': result.stderr,
|
|
261
|
+
'exitCode': result.returncode
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {'error': 'No command specified'}
|
|
265
|
+
|
|
266
|
+
def _handle_exists(self, payload):
|
|
267
|
+
"""Check if path exists"""
|
|
268
|
+
path = payload.get('path')
|
|
269
|
+
return {
|
|
270
|
+
'exists': os.path.exists(path),
|
|
271
|
+
'isFile': os.path.isfile(path),
|
|
272
|
+
'isDirectory': os.path.isdir(path)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
def _handle_mkdir(self, payload):
|
|
276
|
+
"""Create directory"""
|
|
277
|
+
path = payload.get('path')
|
|
278
|
+
recursive = payload.get('recursive', True)
|
|
279
|
+
|
|
280
|
+
if recursive:
|
|
281
|
+
os.makedirs(path, exist_ok=True)
|
|
282
|
+
else:
|
|
283
|
+
os.mkdir(path)
|
|
284
|
+
|
|
285
|
+
return {'success': True, 'path': path}
|
|
286
|
+
|
|
287
|
+
def _handle_remove(self, payload):
|
|
288
|
+
"""Remove file or directory"""
|
|
289
|
+
import shutil
|
|
290
|
+
path = payload.get('path')
|
|
291
|
+
recursive = payload.get('recursive', False)
|
|
292
|
+
|
|
293
|
+
if os.path.isdir(path):
|
|
294
|
+
if recursive:
|
|
295
|
+
shutil.rmtree(path)
|
|
296
|
+
else:
|
|
297
|
+
os.rmdir(path)
|
|
298
|
+
else:
|
|
299
|
+
os.remove(path)
|
|
300
|
+
|
|
301
|
+
return {'success': True}
|
|
302
|
+
|
|
303
|
+
def _handle_copy(self, payload):
|
|
304
|
+
"""Copy file or directory"""
|
|
305
|
+
import shutil
|
|
306
|
+
src = payload.get('src')
|
|
307
|
+
dest = payload.get('dest')
|
|
308
|
+
|
|
309
|
+
if os.path.isdir(src):
|
|
310
|
+
shutil.copytree(src, dest)
|
|
311
|
+
else:
|
|
312
|
+
shutil.copy2(src, dest)
|
|
313
|
+
|
|
314
|
+
return {'success': True, 'dest': dest}
|
|
315
|
+
|
|
316
|
+
def _handle_move(self, payload):
|
|
317
|
+
"""Move/rename file or directory"""
|
|
318
|
+
import shutil
|
|
319
|
+
src = payload.get('src')
|
|
320
|
+
dest = payload.get('dest')
|
|
321
|
+
|
|
322
|
+
shutil.move(src, dest)
|
|
323
|
+
return {'success': True, 'dest': dest}
|
|
324
|
+
|
|
325
|
+
def _handle_dialog_open(self, payload):
|
|
326
|
+
"""Open file dialog"""
|
|
327
|
+
dialog = Gtk.FileChooserDialog(
|
|
328
|
+
title=payload.get('title', 'Open File'),
|
|
329
|
+
parent=self,
|
|
330
|
+
action=Gtk.FileChooserAction.OPEN
|
|
331
|
+
)
|
|
332
|
+
dialog.add_buttons(
|
|
333
|
+
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
334
|
+
Gtk.STOCK_OPEN, Gtk.ResponseType.OK
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# Add filters
|
|
338
|
+
filters = payload.get('filters', [])
|
|
339
|
+
for f in filters:
|
|
340
|
+
file_filter = Gtk.FileFilter()
|
|
341
|
+
file_filter.set_name(f.get('name', 'Files'))
|
|
342
|
+
for ext in f.get('extensions', ['*']):
|
|
343
|
+
file_filter.add_pattern(f'*.{ext}')
|
|
344
|
+
dialog.add_filter(file_filter)
|
|
345
|
+
|
|
346
|
+
response = dialog.run()
|
|
347
|
+
result = None
|
|
348
|
+
|
|
349
|
+
if response == Gtk.ResponseType.OK:
|
|
350
|
+
if payload.get('multiple'):
|
|
351
|
+
result = dialog.get_filenames()
|
|
352
|
+
else:
|
|
353
|
+
result = dialog.get_filename()
|
|
354
|
+
|
|
355
|
+
dialog.destroy()
|
|
356
|
+
return {'path': result}
|
|
357
|
+
|
|
358
|
+
def _handle_dialog_save(self, payload):
|
|
359
|
+
"""Save file dialog"""
|
|
360
|
+
dialog = Gtk.FileChooserDialog(
|
|
361
|
+
title=payload.get('title', 'Save File'),
|
|
362
|
+
parent=self,
|
|
363
|
+
action=Gtk.FileChooserAction.SAVE
|
|
364
|
+
)
|
|
365
|
+
dialog.add_buttons(
|
|
366
|
+
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
|
367
|
+
Gtk.STOCK_SAVE, Gtk.ResponseType.OK
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
dialog.set_do_overwrite_confirmation(True)
|
|
371
|
+
|
|
372
|
+
if payload.get('defaultName'):
|
|
373
|
+
dialog.set_current_name(payload['defaultName'])
|
|
374
|
+
|
|
375
|
+
response = dialog.run()
|
|
376
|
+
result = dialog.get_filename() if response == Gtk.ResponseType.OK else None
|
|
377
|
+
dialog.destroy()
|
|
378
|
+
|
|
379
|
+
return {'path': result}
|
|
380
|
+
|
|
381
|
+
def _handle_dialog_message(self, payload):
|
|
382
|
+
"""Show message dialog"""
|
|
383
|
+
msg_type = {
|
|
384
|
+
'info': Gtk.MessageType.INFO,
|
|
385
|
+
'warning': Gtk.MessageType.WARNING,
|
|
386
|
+
'error': Gtk.MessageType.ERROR,
|
|
387
|
+
'question': Gtk.MessageType.QUESTION
|
|
388
|
+
}.get(payload.get('type', 'info'), Gtk.MessageType.INFO)
|
|
389
|
+
|
|
390
|
+
buttons = Gtk.ButtonsType.OK
|
|
391
|
+
if payload.get('buttons') == 'yesno':
|
|
392
|
+
buttons = Gtk.ButtonsType.YES_NO
|
|
393
|
+
elif payload.get('buttons') == 'okcancel':
|
|
394
|
+
buttons = Gtk.ButtonsType.OK_CANCEL
|
|
395
|
+
|
|
396
|
+
dialog = Gtk.MessageDialog(
|
|
397
|
+
parent=self,
|
|
398
|
+
flags=Gtk.DialogFlags.MODAL,
|
|
399
|
+
message_type=msg_type,
|
|
400
|
+
buttons=buttons,
|
|
401
|
+
text=payload.get('title', ''),
|
|
402
|
+
)
|
|
403
|
+
dialog.format_secondary_text(payload.get('message', ''))
|
|
404
|
+
|
|
405
|
+
response = dialog.run()
|
|
406
|
+
dialog.destroy()
|
|
407
|
+
|
|
408
|
+
return {'response': response == Gtk.ResponseType.OK or response == Gtk.ResponseType.YES}
|
|
409
|
+
|
|
410
|
+
def _handle_app_quit(self, payload):
|
|
411
|
+
"""Quit the application"""
|
|
412
|
+
Gtk.main_quit()
|
|
413
|
+
return {'success': True}
|
|
414
|
+
|
|
415
|
+
def _handle_app_minimize(self, payload):
|
|
416
|
+
"""Minimize the window"""
|
|
417
|
+
self.iconify()
|
|
418
|
+
return {'success': True}
|
|
419
|
+
|
|
420
|
+
def _handle_app_maximize(self, payload):
|
|
421
|
+
"""Toggle maximize"""
|
|
422
|
+
if self.is_maximized():
|
|
423
|
+
self.unmaximize()
|
|
424
|
+
else:
|
|
425
|
+
self.maximize()
|
|
426
|
+
return {'success': True}
|
|
427
|
+
|
|
428
|
+
def _handle_app_get_path(self, payload):
|
|
429
|
+
"""Get system paths with proper localization"""
|
|
430
|
+
import subprocess
|
|
431
|
+
name = payload.get('name', 'home')
|
|
432
|
+
|
|
433
|
+
# Use xdg-user-dir for localized paths
|
|
434
|
+
xdg_mapping = {
|
|
435
|
+
'desktop': 'DESKTOP',
|
|
436
|
+
'documents': 'DOCUMENTS',
|
|
437
|
+
'downloads': 'DOWNLOAD',
|
|
438
|
+
'music': 'MUSIC',
|
|
439
|
+
'pictures': 'PICTURES',
|
|
440
|
+
'videos': 'VIDEOS',
|
|
441
|
+
'templates': 'TEMPLATES',
|
|
442
|
+
'publicshare': 'PUBLICSHARE'
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if name in xdg_mapping:
|
|
446
|
+
try:
|
|
447
|
+
result = subprocess.run(
|
|
448
|
+
['xdg-user-dir', xdg_mapping[name]],
|
|
449
|
+
capture_output=True,
|
|
450
|
+
text=True
|
|
451
|
+
)
|
|
452
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
453
|
+
return {'path': result.stdout.strip()}
|
|
454
|
+
except:
|
|
455
|
+
pass
|
|
456
|
+
|
|
457
|
+
# Fallback paths
|
|
458
|
+
home = os.path.expanduser('~')
|
|
459
|
+
paths = {
|
|
460
|
+
'home': home,
|
|
461
|
+
'temp': '/tmp',
|
|
462
|
+
'app': os.getcwd(),
|
|
463
|
+
'root': '/'
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return {'path': paths.get(name, home)}
|
|
467
|
+
|
|
468
|
+
# ==================== Window Control Handlers ====================
|
|
469
|
+
|
|
470
|
+
def _handle_window_start_drag(self, payload):
|
|
471
|
+
"""Start window drag operation - coordinates from JS"""
|
|
472
|
+
x = payload.get('x', 0)
|
|
473
|
+
y = payload.get('y', 0)
|
|
474
|
+
button = payload.get('button', 1)
|
|
475
|
+
|
|
476
|
+
# Use GLib.idle_add for thread safety
|
|
477
|
+
def do_move():
|
|
478
|
+
try:
|
|
479
|
+
self.begin_move_drag(
|
|
480
|
+
button,
|
|
481
|
+
int(x),
|
|
482
|
+
int(y),
|
|
483
|
+
Gdk.CURRENT_TIME
|
|
484
|
+
)
|
|
485
|
+
except Exception as e:
|
|
486
|
+
print(f"[Aegis] Move drag error: {e}")
|
|
487
|
+
return False
|
|
488
|
+
|
|
489
|
+
GLib.idle_add(do_move)
|
|
490
|
+
return {'success': True}
|
|
491
|
+
|
|
492
|
+
def _handle_window_resize(self, payload):
|
|
493
|
+
"""Start window resize operation"""
|
|
494
|
+
edge = payload.get('edge', 'se')
|
|
495
|
+
x = payload.get('x', 0)
|
|
496
|
+
y = payload.get('y', 0)
|
|
497
|
+
button = payload.get('button', 1)
|
|
498
|
+
|
|
499
|
+
# Map edge name to Gdk.WindowEdge
|
|
500
|
+
edges = {
|
|
501
|
+
'n': Gdk.WindowEdge.NORTH,
|
|
502
|
+
's': Gdk.WindowEdge.SOUTH,
|
|
503
|
+
'e': Gdk.WindowEdge.EAST,
|
|
504
|
+
'w': Gdk.WindowEdge.WEST,
|
|
505
|
+
'ne': Gdk.WindowEdge.NORTH_EAST,
|
|
506
|
+
'nw': Gdk.WindowEdge.NORTH_WEST,
|
|
507
|
+
'se': Gdk.WindowEdge.SOUTH_EAST,
|
|
508
|
+
'sw': Gdk.WindowEdge.SOUTH_WEST
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
gdk_edge = edges.get(edge, Gdk.WindowEdge.SOUTH_EAST)
|
|
512
|
+
|
|
513
|
+
def do_resize():
|
|
514
|
+
try:
|
|
515
|
+
self.begin_resize_drag(
|
|
516
|
+
gdk_edge,
|
|
517
|
+
button,
|
|
518
|
+
int(x),
|
|
519
|
+
int(y),
|
|
520
|
+
Gdk.CURRENT_TIME
|
|
521
|
+
)
|
|
522
|
+
except Exception as e:
|
|
523
|
+
print(f"[Aegis] Resize drag error: {e}")
|
|
524
|
+
return False
|
|
525
|
+
|
|
526
|
+
GLib.idle_add(do_resize)
|
|
527
|
+
return {'success': True}
|
|
528
|
+
|
|
529
|
+
def _handle_window_set_size(self, payload):
|
|
530
|
+
"""Set window size"""
|
|
531
|
+
width = payload.get('width')
|
|
532
|
+
height = payload.get('height')
|
|
533
|
+
|
|
534
|
+
if width and height:
|
|
535
|
+
self.resize(width, height)
|
|
536
|
+
|
|
537
|
+
return {'success': True}
|
|
538
|
+
|
|
539
|
+
def _handle_window_get_size(self, payload):
|
|
540
|
+
"""Get current window size"""
|
|
541
|
+
width, height = self.get_size()
|
|
542
|
+
return {'width': width, 'height': height}
|
|
543
|
+
|
|
544
|
+
def _handle_window_set_position(self, payload):
|
|
545
|
+
"""Set window position"""
|
|
546
|
+
x = payload.get('x')
|
|
547
|
+
y = payload.get('y')
|
|
548
|
+
|
|
549
|
+
if x is not None and y is not None:
|
|
550
|
+
self.move(x, y)
|
|
551
|
+
|
|
552
|
+
return {'success': True}
|
|
553
|
+
|
|
554
|
+
def _handle_window_get_position(self, payload):
|
|
555
|
+
"""Get current window position"""
|
|
556
|
+
x, y = self.get_position()
|
|
557
|
+
return {'x': x, 'y': y}
|
|
558
|
+
|
|
559
|
+
# ==================== Public API ====================
|
|
560
|
+
|
|
561
|
+
def load_file(self, path):
|
|
562
|
+
"""Load HTML file into webview"""
|
|
563
|
+
if not os.path.isabs(path):
|
|
564
|
+
path = os.path.abspath(path)
|
|
565
|
+
self.webview.load_uri(f'file://{path}')
|
|
566
|
+
|
|
567
|
+
def load_url(self, url):
|
|
568
|
+
"""Load URL into webview"""
|
|
569
|
+
self.webview.load_uri(url)
|
|
570
|
+
|
|
571
|
+
def inject_aegis_api(self, preload_path=None):
|
|
572
|
+
"""Inject the Aegis JavaScript API"""
|
|
573
|
+
# Load the Aegis API script
|
|
574
|
+
api_path = os.path.join(
|
|
575
|
+
os.path.dirname(__file__),
|
|
576
|
+
'..', 'runtime', 'aegis-api.js'
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
with open(api_path, 'r') as f:
|
|
580
|
+
api_script = f.read()
|
|
581
|
+
|
|
582
|
+
# Load preload script if provided
|
|
583
|
+
preload_script = ''
|
|
584
|
+
if preload_path and os.path.exists(preload_path):
|
|
585
|
+
with open(preload_path, 'r') as f:
|
|
586
|
+
preload_script = f.read()
|
|
587
|
+
|
|
588
|
+
# Combine scripts
|
|
589
|
+
full_script = api_script + '\n' + preload_script
|
|
590
|
+
|
|
591
|
+
# Inject at document start
|
|
592
|
+
user_script = WebKit2.UserScript(
|
|
593
|
+
full_script,
|
|
594
|
+
WebKit2.UserContentInjectedFrames.ALL_FRAMES,
|
|
595
|
+
WebKit2.UserScriptInjectionTime.START,
|
|
596
|
+
None, None
|
|
597
|
+
)
|
|
598
|
+
self.content_manager.add_script(user_script)
|
|
599
|
+
|
|
600
|
+
def run(self):
|
|
601
|
+
"""Show window and start main loop"""
|
|
602
|
+
self.show_all()
|
|
603
|
+
Gtk.main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Aegis Runtime - JavaScript API injection"""
|