source: src/mlx/gui/cef.py@ 1096:3b923185a50d

python3
Last change on this file since 1096:3b923185a50d was 1096:3b923185a50d, checked in by István Váradi <ivaradi@…>, 10 months ago

The CEF cache directory is removed on startup

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