source: src/mlx/gui/cef.py@ 1099:a2d5a4d1c6c1

python3
Last change on this file since 1099:a2d5a4d1c6c1 was 1099:a2d5a4d1c6c1, checked in by István Váradi <ivaradi@…>, 9 months ago

Various minor bugfixes

File size: 14.8 KB
Line 
1from .common import *
2
3from mlx.util import secondaryInstallation
4
5from cefpython3 import cefpython
6
7import platform
8import json
9import time
10import os
11import re
12import _thread
13import threading
14import tempfile
15import traceback
16import ctypes
17import urllib.request, urllib.error, urllib.parse
18from lxml import etree
19from io import StringIO
20import lxml.html
21import shutil
22
23#------------------------------------------------------------------------------
24
25## @package mlx.gui.cef
26#
27# Some helper stuff related to the Chrome Embedded Framework
28
29#------------------------------------------------------------------------------
30
31# Indicate if we should quit
32_toQuit = False
33
34# The SimBrief handler
35_simBriefHandler = None
36
37# The cache directory
38cacheDir = os.path.join(GLib.get_user_cache_dir(), "mlxcef")
39
40#------------------------------------------------------------------------------
41
42SIMBRIEF_PROGRESS_SEARCHING_BROWSER = 1
43SIMBRIEF_PROGRESS_LOADING_FORM = 2
44SIMBRIEF_PROGRESS_FILLING_FORM = 3
45SIMBRIEF_PROGRESS_WAITING_LOGIN = 4
46SIMBRIEF_PROGRESS_LOGGING_IN = 5
47SIMBRIEF_PROGRESS_WAITING_RESULT = 6
48
49SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING = 7
50SIMBRIEF_PROGRESS_DONE = 1000
51
52SIMBRIEF_RESULT_NONE = 0
53SIMBRIEF_RESULT_OK = 1
54SIMBRIEF_RESULT_ERROR_OTHER = 2
55SIMBRIEF_RESULT_ERROR_NO_FORM = 11
56SIMBRIEF_RESULT_ERROR_NO_POPUP = 12
57SIMBRIEF_RESULT_ERROR_LOGIN_FAILED = 13
58
59#------------------------------------------------------------------------------
60
61class SimBriefHandler(object):
62 """An object to store the state of a SimBrief query."""
63 _formURLBase = MAVA_BASE_URL + "/simbrief_form.php"
64
65 _resultURLBase = MAVA_BASE_URL + "/simbrief_briefing.php"
66
67 @staticmethod
68 def getFormURL(plan):
69 """Get the form URL for the given plan."""
70 return SimBriefHandler._formURLBase + "?" + \
71 urllib.parse.urlencode(plan)
72
73 _querySettings = {
74 'navlog': True,
75 'etops': True,
76 'stepclimbs': True,
77 'tlr': True,
78 'notams': True,
79 'firnot': True,
80 'maps': 'Simple',
81 };
82
83
84 def __init__(self):
85 """Construct the handler."""
86 self._browser = None
87 self._plan = None
88 self._getCredentials = None
89 self._getCredentialsCount = 0
90 self._updateProgressFn = None
91 self._htmlFilePath = None
92 self._lastProgress = SIMBRIEF_PROGRESS_SEARCHING_BROWSER
93 self._timeoutID = None
94
95 def initialize(self):
96 """Create and initialize the browser used for Simbrief."""
97 windowInfo = cefpython.WindowInfo()
98 windowInfo.SetAsOffscreen(int(0))
99
100 self._browser = \
101 cefpython.CreateBrowserSync(windowInfo, browserSettings = {})
102 self._browser.SetClientHandler(OffscreenRenderHandler())
103 self._browser.SetFocus(True)
104 self._browser.SetClientCallback("OnLoadEnd", self._onLoadEnd)
105 self._browser.SetClientCallback("OnLoadError", self._onLoadError)
106 self._browser.SetClientCallback("OnBeforeBrowse",
107 lambda browser, frame, request,
108 user_gesture, is_redirect: False)
109
110 def call(self, plan, getCredentials, updateProgress, htmlFilePath):
111 """Call SimBrief with the given plan."""
112 self._timeoutID = GObject.timeout_add(120*1000, self._timedOut)
113
114 self._plan = plan
115 self._getCredentials = getCredentials
116 self._getCredentialsCount = 0
117 self._updateProgressFn = updateProgress
118 self._htmlFilePath = htmlFilePath
119
120 self._browser.LoadUrl(SimBriefHandler.getFormURL(plan))
121 self._updateProgress(SIMBRIEF_PROGRESS_LOADING_FORM,
122 SIMBRIEF_RESULT_NONE, None)
123
124 def finalize(self):
125 """Close the browser and release it."""
126 if self._browser is not None:
127 self._browser.CloseBrowser(True)
128 self._browser = None
129
130 def _onLoadEnd(self, browser, frame, http_code):
131 """Called when a page has been loaded in the SimBrief browser."""
132 url = frame.GetUrl()
133 print("gui.cef.SimBriefHandler._onLoadEnd", http_code, url)
134 if http_code>=300:
135 self._updateProgress(self._lastProgress,
136 SIMBRIEF_RESULT_ERROR_OTHER, None)
137 elif url.startswith(SimBriefHandler._formURLBase):
138 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
139 SIMBRIEF_RESULT_NONE, None)
140 elif url.startswith("https://www.simbrief.com/system/login.api.sso.php"):
141 js = "document.getElementsByClassName(\"login_option navigraph\")[0].click();"
142 frame.ExecuteJavascript(js)
143 elif url.startswith("https://identity.api.navigraph.com/login?"):
144 (user, password) = self._getCredentials(self._getCredentialsCount)
145 if user is None or password is None:
146 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
147 SIMBRIEF_RESULT_ERROR_LOGIN_FAILED, None)
148 return
149
150 self._getCredentialsCount += 1
151
152 js = "form=document.getElementsByName(\"form\")[0];"
153 js +="form.username.value=\"" + user + "\";"
154 js +="form.password.value=\"" + password + "\";"
155 js +="form.submit();"
156 frame.ExecuteJavascript(js)
157 elif url.startswith("https://www.simbrief.com/ofp/ofp.loader.api.php"):
158 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_RESULT,
159 SIMBRIEF_RESULT_NONE, None)
160 elif url.startswith(SimBriefHandler._resultURLBase):
161 self._updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
162 SIMBRIEF_RESULT_OK, None)
163
164 def _onLoadError(self, browser, frame, error_code, error_text_out,
165 failed_url):
166 """Called when loading of an URL fails."""
167 print("gui.cef.SimBriefHandler._onLoadError", browser, frame, error_code, error_text_out, failed_url)
168 if error_code==-3 and \
169 failed_url.startswith("https://identity.api.navigraph.com/connect/authorize"):
170 self._browser.LoadUrl(SimBriefHandler.getFormURL(self._plan))
171 self._updateProgress(SIMBRIEF_PROGRESS_LOADING_FORM,
172 SIMBRIEF_RESULT_NONE, None)
173 else:
174 self._updateProgress(self._lastProgress,
175 SIMBRIEF_RESULT_ERROR_OTHER, None)
176
177 def _updateProgress(self, progress, results, flightInfo):
178 """Update the progress."""
179 self._lastProgress = progress
180 if results!=SIMBRIEF_RESULT_NONE:
181 if self._timeoutID is not None:
182 GObject.source_remove(self._timeoutID)
183 self._plan = None
184
185 if self._updateProgressFn is not None:
186 self._updateProgressFn(progress, results, flightInfo)
187
188 def _timedOut(self):
189 """Called when the timeout occurs."""
190 if self._lastProgress==SIMBRIEF_PROGRESS_LOADING_FORM:
191 result = SIMBRIEF_RESULT_ERROR_NO_FORM
192 elif self._lastProgress==SIMBRIEF_PROGRESS_WAITING_LOGIN:
193 result = SIMBRIEF_RESULT_ERROR_NO_POPUP
194 else:
195 result = SIMBRIEF_RESULT_ERROR_OTHER
196
197 self._updateProgress(self._lastProgress, result, None)
198
199 return False
200
201#------------------------------------------------------------------------------
202
203def initialize(initializedCallback):
204 """Initialize the Chrome Embedded Framework."""
205 global _toQuit, _simBriefHandler
206 _toQuit = False
207
208 GObject.threads_init()
209
210 _simBriefHandler = SimBriefHandler()
211 _initializeCEF([], initializedCallback)
212
213#------------------------------------------------------------------------------
214
215def _initializeCEF(args, initializedCallback):
216 """Perform the actual initialization of CEF using the given arguments."""
217 if os.name=="nt":
218 _initializeCEF1(args, initializedCallback)
219 else:
220 GObject.timeout_add(100, _initializeCEF1, args, initializedCallback)
221
222#------------------------------------------------------------------------------
223
224def _initializeCEF1(args, initializedCallback):
225 """Perform the actual initialization of CEF using the given arguments."""
226 print("Initializing CEF with args:", args)
227
228 settings = {
229 "debug": True, # cefpython debug messages in console and in log_file
230 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
231 "log_file": "", # Set to "" to disable
232 "release_dcheck_enabled": True, # Enable only when debugging
233 # This directories must be set on Linux
234 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
235 "resources_dir_path": cefpython.GetModuleDirectory(),
236 "browser_subprocess_path": "%s/%s" % \
237 (cefpython.GetModuleDirectory(), "subprocess"),
238 "windowless_rendering_enabled": True,
239 "cache_path": cacheDir,
240 "persist_session_cookies": True,
241 "persist_user_preferences": True
242 }
243
244 switches={}
245 for arg in args:
246 if arg.startswith("--"):
247 if arg != "--enable-logging":
248 assignIndex = arg.find("=")
249 if assignIndex<0:
250 switches[arg[2:]] = ""
251 else:
252 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
253 else:
254 print("Unhandled switch", arg)
255
256 cefpython.Initialize(settings, switches)
257
258 GObject.timeout_add(10, _handleTimeout)
259
260 print("Initialized, executing callback...")
261 initializedCallback()
262 return False
263
264#------------------------------------------------------------------------------
265
266def getContainer():
267 """Get a container object suitable for running a browser instance
268 within."""
269 container = Gtk.DrawingArea()
270 container.set_property("can-focus", True)
271 container.connect("size-allocate", _handleSizeAllocate)
272 container.show()
273
274 return container
275
276#------------------------------------------------------------------------------
277
278def startInContainer(container, url, browserSettings = {}):
279 """Start a browser instance in the given container with the given URL."""
280 if os.name=="nt":
281 Gdk.threads_enter()
282 ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
283 ctypes.pythonapi.PyCapsule_GetPointer.argtypes = \
284 [ctypes.py_object]
285 gpointer = ctypes.pythonapi.PyCapsule_GetPointer(
286 container.get_property("window").__gpointer__, None)
287 libgdk = ctypes.CDLL("libgdk-3-0.dll")
288 windowID = libgdk.gdk_win32_window_get_handle(gpointer)
289 container.windowID = windowID
290 Gdk.threads_leave()
291 windowRect = [0, 0, 1, 1]
292 else:
293 container.set_visual(container.get_screen().lookup_visual(0x21))
294 windowID = container.get_window().get_xid()
295 windowRect = None
296
297 windowInfo = cefpython.WindowInfo()
298 if windowID is not None:
299 windowInfo.SetAsChild(windowID, windowRect = windowRect)
300
301 browser = cefpython.CreateBrowserSync(windowInfo,
302 browserSettings = browserSettings,
303 navigateUrl = url)
304 container.browser = browser
305
306 return browser
307
308#------------------------------------------------------------------------------
309
310class OffscreenRenderHandler(object):
311 def GetRootScreenRect(self, browser, rect_out):
312 #print "GetRootScreenRect"
313 rect_out += [0, 0, 1920, 1080]
314 return True
315
316 def GetViewRect(self, browser, rect_out):
317 #print "GetViewRect"
318 rect_out += [0, 40, 1920, 1040]
319 return True
320
321 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
322 #print "GetScreenPoint", viewX, viewY
323 rect += [viewX, viewY]
324 return True
325
326 def GetScreenInfo(self, browser, screenInfo):
327 #print "GetScreenInfo"
328 pass
329
330 def OnPopupShow(self, browser, show):
331 #print "OnPopupShow", show
332 pass
333
334 def OnPopupSize(self, browser, rect):
335 #print "OnPopupSize", rect
336 pass
337
338 def OnPaint(self, browser, element_type, dirty_rects, paint_buffer, width, height):
339 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
340 pass
341
342 def OnCursorChange(self, browser, cursor):
343 #print "OnCursorChange", cursor
344 pass
345
346 def OnScrollOffsetChanged(self, browser):
347 #print "OnScrollOffsetChange"
348 pass
349
350 def OnBeforePopup(self, browser, frame, target_url, target_frame_name,
351 target_disposition, user_gesture,
352 popup_features, window_info_out, client,
353 browser_settings_out, no_javascript_access_out,
354 **kwargs):
355 wInfo = cefpython.WindowInfo()
356 wInfo.SetAsOffscreen(int(0))
357
358 window_info_out.append(wInfo)
359
360 return False
361
362#------------------------------------------------------------------------------
363
364def initializeSimBrief():
365 """Initialize the (hidden) browser window for SimBrief."""
366 _simBriefHandler.initialize()
367
368#------------------------------------------------------------------------------
369
370def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
371 """Call SimBrief with the given plan.
372
373 The callbacks will be called in the main GUI thread."""
374 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
375
376#------------------------------------------------------------------------------
377
378def finalizeSimBrief():
379 """Finallize the (hidden) browser window for SimBrief."""
380 if _simBriefHandler is not None:
381 _simBriefHandler.finalize()
382
383#------------------------------------------------------------------------------
384
385def finalize():
386 """Finalize the Chrome Embedded Framework."""
387 global _toQuit
388 _toQuit = True
389
390 if os.name!="nt":
391 cefpython.Shutdown()
392
393#------------------------------------------------------------------------------
394
395def clearCache():
396 """Clear the CEF cache directory."""
397 try:
398 shutil.rmtree(cacheDir)
399 print("gui.cef.clearCache: the cache directory is removed")
400 except Exception as e:
401 print("gui.cef.clearCache: cache directory removal failed:", e)
402
403#------------------------------------------------------------------------------
404
405def _handleTimeout():
406 """Handle the timeout by running the CEF message loop."""
407 if _toQuit:
408 return False
409 else:
410 cefpython.MessageLoopWork()
411 return True
412
413#------------------------------------------------------------------------------
414
415def _handleSizeAllocate(widget, sizeAlloc):
416 """Handle the size-allocate event on Windows."""
417 if os.name=="nt":
418 if widget is not None and hasattr(widget, "windowID"):
419 cefpython.WindowUtils.OnSize(widget.windowID, 0, 0, 0)
420 else:
421 if widget is not None and hasattr(widget, "browser") and \
422 widget.browser is not None:
423 widget.browser.SetBounds(sizeAlloc.x, sizeAlloc.y,
424 sizeAlloc.width, sizeAlloc.height)
Note: See TracBrowser for help on using the repository browser.