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
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 urllib.request, urllib.error, urllib.parse
17from lxml import etree
18from io import StringIO
19import lxml.html
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
32# The SimBrief handler
33_simBriefHandler = None
34
35#------------------------------------------------------------------------------
36
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
43
44SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING = 7
45SIMBRIEF_PROGRESS_DONE = 1000
46
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
53
54#------------------------------------------------------------------------------
55
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"
59
60 _querySettings = {
61 'navlog': True,
62 'etops': True,
63 'stepclimbs': True,
64 'tlr': True,
65 'notams': True,
66 'firnot': True,
67 'maps': 'Simple',
68 };
69
70
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
81
82 def initialize(self):
83 """Create and initialize the browser used for Simbrief."""
84 windowInfo = cefpython.WindowInfo()
85 windowInfo.SetAsOffscreen(int(0))
86
87 self._browser = \
88 cefpython.CreateBrowserSync(windowInfo, browserSettings = {},
89 navigateUrl = SimBriefHandler._formURL)
90 self._browser.SetClientHandler(OffscreenRenderHandler())
91 self._browser.SetFocus(True)
92 self._browser.SetClientCallback("OnLoadEnd", self._onLoadEnd)
93
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):
101 """Call SimBrief with the given plan."""
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
114 def _onLoadEnd(self, browser, frame, http_code):
115 """Called when a page has been loaded in the SimBrief browser."""
116 url = frame.GetUrl()
117 print("gui.cef.SimBriefHandler._onLoadEnd", http_code, url)
118 if http_code>=300:
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)
127
128 js = "form=document.getElementById(\"sbapiform\");"
129 for (name, value) in self._plan.items():
130 js += "form." + name + ".value=\"" + value + "\";"
131 for (name, value) in SimBriefHandler._querySettings.items():
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();"
140
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
148
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)
167
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,))
177 _thread.daemon = True
178 _thread.start()
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.
186
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:
196 if self._timeoutID is not None:
197 gobject.source_remove(self._timeoutID)
198 self._plan = None
199
200 if self._updateProgressFn is not None:
201 self._updateProgressFn(progress, results, flightInfo)
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
211
212 self._updateProgress(self._lastProgress, result, None)
213
214 return False
215
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
223 response = urllib.request.urlopen(link)
224 xmlContent = response.read()
225 # Processing xml
226 content = etree.iterparse(StringIO(xmlContent))
227
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
236
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
246 print((sorted(availableInfo.keys())))
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)
253
254#------------------------------------------------------------------------------
255
256def initialize(initializedCallback):
257 """Initialize the Chrome Embedded Framework."""
258 global _toQuit, _simBriefHandler
259 _toQuit = False
260
261 gobject.threads_init()
262
263 _simBriefHandler = SimBriefHandler()
264 _initializeCEF([], initializedCallback)
265
266#------------------------------------------------------------------------------
267
268def _initializeCEF(args, initializedCallback):
269 """Perform the actual initialization of CEF using the given arguments."""
270 print("Initializing CEF with args:", args)
271
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
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:
294 print("Unhandled switch", arg)
295
296 cefpython.Initialize(settings, switches)
297
298 gobject.timeout_add(10, _handleTimeout)
299
300 print("Initialized, executing callback...")
301 initializedCallback()
302
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:
313 container = gtk.DrawingArea()
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":
324 window = container.get_window()
325 if window is None:
326 print("mlx.gui.cef.startInContainer: no window found!")
327 windowID = None
328 else:
329 windowID = window.handle
330 else:
331 container.set_visual(container.get_screen().lookup_visual(0x21))
332 windowID = container.get_window().get_xid()
333
334 windowInfo = cefpython.WindowInfo()
335 if windowID is not None:
336 windowInfo.SetAsChild(windowID)
337
338 return cefpython.CreateBrowserSync(windowInfo,
339 browserSettings = browserSettings,
340 navigateUrl = url)
341
342#------------------------------------------------------------------------------
343
344class OffscreenRenderHandler(object):
345 def GetRootScreenRect(self, browser, rect_out):
346 #print "GetRootScreenRect"
347 rect_out += [0, 0, 800, 600]
348 return True
349
350 def GetViewRect(self, browser, rect_out):
351 #print "GetViewRect"
352 rect_out += [0, 0, 800, 600]
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
372 def OnPaint(self, browser, element_type, dirty_rects, paint_buffer, width, height):
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."""
398 _simBriefHandler.initialize()
399
400#------------------------------------------------------------------------------
401
402def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
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)
407
408#------------------------------------------------------------------------------
409
410def finalize():
411 """Finalize the Chrome Embedded Framework."""
412 global _toQuit
413 _toQuit = True
414 cefpython.Shutdown()
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."""
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.