source: src/mlx/gui/cef.py@ 944:ebf5b88d3fd2

python3
Last change on this file since 944:ebf5b88d3fd2 was 944:ebf5b88d3fd2, checked in by István Váradi <ivaradi@…>, 5 years ago

Extra checks in the CEF code (re #347).

File size: 15.2 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
[919]16import urllib.request, urllib.error, urllib.parse
[805]17from lxml import etree
[919]18from io import StringIO
[805]19import lxml.html
[648]20
21#------------------------------------------------------------------------------
22
23## @package mlx.gui.cef
24#
25# Some helper stuff related to the Chrome Embedded Framework
26
27#------------------------------------------------------------------------------
28
29# Indicate if we should quit
30_toQuit = False
31
[805]32# The SimBrief handler
33_simBriefHandler = None
[682]34
35#------------------------------------------------------------------------------
36
[805]37SIMBRIEF_PROGRESS_SEARCHING_BROWSER = 1
38SIMBRIEF_PROGRESS_LOADING_FORM = 2
39SIMBRIEF_PROGRESS_FILLING_FORM = 3
40SIMBRIEF_PROGRESS_WAITING_LOGIN = 4
41SIMBRIEF_PROGRESS_LOGGING_IN = 5
42SIMBRIEF_PROGRESS_WAITING_RESULT = 6
[682]43
[805]44SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING = 7
45SIMBRIEF_PROGRESS_DONE = 1000
[682]46
[805]47SIMBRIEF_RESULT_NONE = 0
48SIMBRIEF_RESULT_OK = 1
49SIMBRIEF_RESULT_ERROR_OTHER = 2
50SIMBRIEF_RESULT_ERROR_NO_FORM = 11
51SIMBRIEF_RESULT_ERROR_NO_POPUP = 12
52SIMBRIEF_RESULT_ERROR_LOGIN_FAILED = 13
[682]53
[648]54#------------------------------------------------------------------------------
55
[805]56class SimBriefHandler(object):
57 """An object to store the state of a SimBrief query."""
58 _formURL = "http://flare.privatedns.org/mava_simbrief/simbrief_form.html"
[692]59
[805]60 _querySettings = {
61 'navlog': True,
62 'etops': True,
63 'stepclimbs': True,
64 'tlr': True,
65 'notams': True,
66 'firnot': True,
67 'maps': 'Simple',
68 };
[682]69
70
[805]71 def __init__(self):
72 """Construct the handler."""
73 self._browser = None
74 self._plan = None
75 self._getCredentials = None
76 self._getCredentialsCount = 0
77 self._updateProgressFn = None
78 self._htmlFilePath = None
79 self._lastProgress = SIMBRIEF_PROGRESS_SEARCHING_BROWSER
80 self._timeoutID = None
[682]81
[805]82 def initialize(self):
[695]83 """Create and initialize the browser used for Simbrief."""
84 windowInfo = cefpython.WindowInfo()
85 windowInfo.SetAsOffscreen(int(0))
86
[805]87 self._browser = \
[695]88 cefpython.CreateBrowserSync(windowInfo, browserSettings = {},
[805]89 navigateUrl = SimBriefHandler._formURL)
90 self._browser.SetClientHandler(OffscreenRenderHandler())
91 self._browser.SetFocus(True)
92 self._browser.SetClientCallback("OnLoadEnd", self._onLoadEnd)
[695]93
[805]94 bindings = cefpython.JavascriptBindings(bindToFrames=True,
95 bindToPopups=True)
96 bindings.SetFunction("briefingData", self._briefingDataAvailable)
97 bindings.SetFunction("formFilled", self._formFilled)
98 self._browser.SetJavascriptBindings(bindings)
99
100 def call(self, plan, getCredentials, updateProgress, htmlFilePath):
[685]101 """Call SimBrief with the given plan."""
[805]102 self._timeoutID = gobject.timeout_add(120*1000, self._timedOut)
103
104 self._plan = plan
105 self._getCredentials = getCredentials
106 self._getCredentialsCount = 0
107 self._updateProgressFn = updateProgress
108 self._htmlFilePath = htmlFilePath
109
110 self._browser.LoadUrl(SimBriefHandler._formURL)
111 self._updateProgress(SIMBRIEF_PROGRESS_LOADING_FORM,
112 SIMBRIEF_RESULT_NONE, None)
113
[943]114 def _onLoadEnd(self, browser, frame, http_code):
[805]115 """Called when a page has been loaded in the SimBrief browser."""
116 url = frame.GetUrl()
[943]117 print("gui.cef.SimBriefHandler._onLoadEnd", http_code, url)
118 if http_code>=300:
[805]119 self._updateProgress(self._lastProgress,
120 SIMBRIEF_RESULT_ERROR_OTHER, None)
121 elif url.startswith("http://flare.privatedns.org/mava_simbrief/simbrief_form.html"):
122 if self._plan is None:
123 return
124
125 self._updateProgress(SIMBRIEF_PROGRESS_FILLING_FORM,
126 SIMBRIEF_RESULT_NONE, None)
[685]127
[805]128 js = "form=document.getElementById(\"sbapiform\");"
[919]129 for (name, value) in self._plan.items():
[805]130 js += "form." + name + ".value=\"" + value + "\";"
[919]131 for (name, value) in SimBriefHandler._querySettings.items():
[805]132 if isinstance(value, bool):
133 js += "form." + name + ".checked=" + \
134 ("true" if value else "false") + ";"
135 elif isinstance(value, str):
136 js += "form." + name + ".value=\"" + value + "\";"
137
138 js += "form.submitform.click();"
139 js += "window.formFilled();"
[682]140
[805]141 frame.ExecuteJavascript(js)
142 elif url.startswith("http://www.simbrief.com/system/login.api.php"):
143 (user, password) = self._getCredentials(self._getCredentialsCount)
144 if user is None or password is None:
145 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
146 SIMBRIEF_RESULT_ERROR_LOGIN_FAILED, None)
147 return
[682]148
[805]149 self._getCredentialsCount += 1
150 js = "form=document.forms[0];"
151 js += "form.user.value=\"" + user + "\";"
152 js += "form.pass.value=\"" + password + "\";"
153 js += "form.submit();"
154 frame.ExecuteJavascript(js)
155 elif url.startswith("http://www.simbrief.com/ofp/ofp.loader.api.php"):
156 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_RESULT,
157 SIMBRIEF_RESULT_NONE, None)
158 elif url.startswith("http://flare.privatedns.org/mava_simbrief/simbrief_briefing.php"):
159 js = "form=document.getElementById(\"hiddenform\");"
160 js += "window.briefingData(form.hidden_is_briefing_available.value, form.hidden_link.value);";
161 frame.ExecuteJavascript(js)
162
163 def _formFilled(self):
164 """Called when the form has been filled and submitted."""
165 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
166 SIMBRIEF_RESULT_NONE, None)
[682]167
[805]168 def _briefingDataAvailable(self, available, link):
169 """Called when the briefing data is available."""
170 if available:
171 link ="http://www.simbrief.com/ofp/flightplans/xml/" + link + ".xml"
172
173 self._updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
174 SIMBRIEF_RESULT_NONE, None)
175
176 thread = threading.Thread(target = self._getResults, args = (link,))
[919]177 _thread.daemon = True
178 _thread.start()
[805]179 else:
180 self._updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
181 SIMBRIEF_RESULT_ERROR_OTHER, None)
182
183 def _resultsAvailable(self, flightInfo):
184 """Called from the result retrieval thread when the result is
185 available.
[696]186
[805]187 It checks for the plan being not None, as we may time out."""
188 if self._plan is not None:
189 self._updateProgress(SIMBRIEF_PROGRESS_DONE,
190 SIMBRIEF_RESULT_OK, flightInfo)
191
192 def _updateProgress(self, progress, results, flightInfo):
193 """Update the progress."""
194 self._lastProgress = progress
195 if results!=SIMBRIEF_RESULT_NONE:
[944]196 if self._timeoutID is not None:
197 gobject.source_remove(self._timeoutID)
[805]198 self._plan = None
199
[944]200 if self._updateProgressFn is not None:
201 self._updateProgressFn(progress, results, flightInfo)
[805]202
203 def _timedOut(self):
204 """Called when the timeout occurs."""
205 if self._lastProgress==SIMBRIEF_PROGRESS_LOADING_FORM:
206 result = SIMBRIEF_RESULT_ERROR_NO_FORM
207 elif self._lastProgress==SIMBRIEF_PROGRESS_WAITING_LOGIN:
208 result = SIMBRIEF_RESULT_ERROR_NO_POPUP
209 else:
210 result = SIMBRIEF_RESULT_ERROR_OTHER
[685]211
[805]212 self._updateProgress(self._lastProgress, result, None)
213
214 return False
[685]215
[805]216 def _getResults(self, link):
217 """Get the result from the given link."""
218 availableInfo = {}
219 ## Holds analysis data to be used
220 flightInfo = {}
221
222 # Obtaining the xml
[919]223 response = urllib.request.urlopen(link)
[805]224 xmlContent = response.read()
225 # Processing xml
226 content = etree.iterparse(StringIO(xmlContent))
[685]227
[805]228 for (action, element) in content:
229 # Processing tags that occur multiple times
230 if element.tag == "weather":
231 weatherElementList = list(element)
232 for weatherElement in weatherElementList:
233 flightInfo[weatherElement.tag] = weatherElement.text
234 else:
235 availableInfo[element.tag] = element.text
[685]236
[805]237 # Processing plan_html
238 ## Obtaining chart links
239 imageLinks = []
240 for imageLinkElement in lxml.html.find_class(availableInfo["plan_html"],
241 "ofpmaplink"):
242 for imageLink in imageLinkElement.iterlinks():
243 if imageLink[1] == 'src':
244 imageLinks.append(imageLink[2])
245 flightInfo["image_links"] = imageLinks
[919]246 print((sorted(availableInfo.keys())))
[805]247 htmlFilePath = "simbrief_plan.html" if self._htmlFilePath is None \
248 else self._htmlFilePath
249 with open(htmlFilePath, 'w') as f:
250 f.write(availableInfo["plan_html"])
251
252 gobject.idle_add(self._resultsAvailable, flightInfo)
[682]253
254#------------------------------------------------------------------------------
255
[805]256def initialize(initializedCallback):
[648]257 """Initialize the Chrome Embedded Framework."""
[805]258 global _toQuit, _simBriefHandler
[648]259 _toQuit = False
260
261 gobject.threads_init()
262
[805]263 _simBriefHandler = SimBriefHandler()
264 _initializeCEF([], initializedCallback)
[682]265
266#------------------------------------------------------------------------------
267
268def _initializeCEF(args, initializedCallback):
269 """Perform the actual initialization of CEF using the given arguments."""
[919]270 print("Initializing CEF with args:", args)
[682]271
[648]272 settings = {
273 "debug": True, # cefpython debug messages in console and in log_file
274 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
275 "log_file": "", # Set to "" to disable
276 "release_dcheck_enabled": True, # Enable only when debugging
277 # This directories must be set on Linux
278 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
279 "resources_dir_path": cefpython.GetModuleDirectory(),
280 "browser_subprocess_path": "%s/%s" % \
281 (cefpython.GetModuleDirectory(), "subprocess"),
282 }
283
[682]284 switches={}
285 for arg in args:
286 if arg.startswith("--"):
287 if arg != "--enable-logging":
288 assignIndex = arg.find("=")
289 if assignIndex<0:
290 switches[arg[2:]] = ""
291 else:
292 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
293 else:
[919]294 print("Unhandled switch", arg)
[682]295
296 cefpython.Initialize(settings, switches)
[648]297
298 gobject.timeout_add(10, _handleTimeout)
299
[919]300 print("Initialized, executing callback...")
[682]301 initializedCallback()
302
[648]303#------------------------------------------------------------------------------
304
305def getContainer():
306 """Get a container object suitable for running a browser instance
307 within."""
308 if os.name=="nt":
309 container = gtk.DrawingArea()
310 container.set_property("can-focus", True)
311 container.connect("size-allocate", _handleSizeAllocate)
312 else:
[913]313 container = gtk.DrawingArea()
[648]314
315 container.show()
316
317 return container
318
319#------------------------------------------------------------------------------
320
321def startInContainer(container, url, browserSettings = {}):
322 """Start a browser instance in the given container with the given URL."""
323 if os.name=="nt":
[805]324 window = container.get_window()
325 if window is None:
[919]326 print("mlx.gui.cef.startInContainer: no window found!")
[805]327 windowID = None
328 else:
329 windowID = window.handle
[648]330 else:
[924]331 container.set_visual(container.get_screen().lookup_visual(0x21))
332 windowID = container.get_window().get_xid()
[648]333
334 windowInfo = cefpython.WindowInfo()
[805]335 if windowID is not None:
336 windowInfo.SetAsChild(windowID)
[648]337
338 return cefpython.CreateBrowserSync(windowInfo,
339 browserSettings = browserSettings,
340 navigateUrl = url)
341
342#------------------------------------------------------------------------------
343
[685]344class OffscreenRenderHandler(object):
[943]345 def GetRootScreenRect(self, browser, rect_out):
[685]346 #print "GetRootScreenRect"
[943]347 rect_out += [0, 0, 800, 600]
[685]348 return True
349
[943]350 def GetViewRect(self, browser, rect_out):
[685]351 #print "GetViewRect"
[943]352 rect_out += [0, 0, 800, 600]
[685]353 return True
354
355 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
356 #print "GetScreenPoint", viewX, viewY
357 rect += [viewX, viewY]
358 return True
359
360 def GetScreenInfo(self, browser, screenInfo):
361 #print "GetScreenInfo"
362 pass
363
364 def OnPopupShow(self, browser, show):
365 #print "OnPopupShow", show
366 pass
367
368 def OnPopupSize(self, browser, rect):
369 #print "OnPopupSize", rect
370 pass
371
[943]372 def OnPaint(self, browser, element_type, dirty_rects, paint_buffer, width, height):
[685]373 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
374 pass
375
376 def OnCursorChange(self, browser, cursor):
377 #print "OnCursorChange", cursor
378 pass
379
380 def OnScrollOffsetChanged(self, browser):
381 #print "OnScrollOffsetChange"
382 pass
383
384 def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
385 popupFeatures, windowInfo, client, browserSettings,
386 noJavascriptAccess):
387 wInfo = cefpython.WindowInfo()
388 wInfo.SetAsOffscreen(int(0))
389
390 windowInfo.append(wInfo)
391
392 return False
393
394#------------------------------------------------------------------------------
395
396def initializeSimBrief():
397 """Initialize the (hidden) browser window for SimBrief."""
[805]398 _simBriefHandler.initialize()
[685]399
400#------------------------------------------------------------------------------
401
402def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
[805]403 """Call SimBrief with the given plan.
404
405 The callbacks will be called in the main GUI thread."""
406 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
[685]407
408#------------------------------------------------------------------------------
409
[648]410def finalize():
411 """Finalize the Chrome Embedded Framework."""
[805]412 global _toQuit
413 _toQuit = True
[702]414 cefpython.Shutdown()
[648]415
416#------------------------------------------------------------------------------
417
418def _handleTimeout():
419 """Handle the timeout by running the CEF message loop."""
420 if _toQuit:
421 return False
422 else:
423 cefpython.MessageLoopWork()
424 return True
425
426#------------------------------------------------------------------------------
427
428def _handleSizeAllocate(widget, sizeAlloc):
429 """Handle the size-allocate event."""
[805]430 window = widget.get_window()
431 if widget is not None:
432 cefpython.WindowUtils.OnSize(window.handle, 0, 0, 0)
Note: See TracBrowser for help on using the repository browser.