source: src/mlx/gui/cef.py@ 915:20ac481b1ddf

version_0.39_maint
Last change on this file since 915:20ac481b1ddf was 915:20ac481b1ddf, checked in by István Váradi <ivaradi@…>, 5 years ago

Dummy change retracted

File size: 14.9 KB
RevLine 
[648]1from common import *
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
[805]12import thread
[682]13import threading
14import tempfile
15import traceback
[805]16import urllib2
17from lxml import etree
18from StringIO import StringIO
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
114 def _onLoadEnd(self, browser, frame, httpCode):
115 """Called when a page has been loaded in the SimBrief browser."""
116 url = frame.GetUrl()
117 print "gui.cef.SimBriefHandler._onLoadEnd", httpCode, url
118 if httpCode>=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)
[685]127
[805]128 js = "form=document.getElementById(\"sbapiform\");"
129 for (name, value) in self._plan.iteritems():
130 js += "form." + name + ".value=\"" + value + "\";"
131 for (name, value) in SimBriefHandler._querySettings.iteritems():
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,))
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.
[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:
196 gobject.source_remove(self._timeoutID)
197 self._plan = None
198
199 self._updateProgressFn(progress, results, flightInfo)
200
201 def _timedOut(self):
202 """Called when the timeout occurs."""
203 if self._lastProgress==SIMBRIEF_PROGRESS_LOADING_FORM:
204 result = SIMBRIEF_RESULT_ERROR_NO_FORM
205 elif self._lastProgress==SIMBRIEF_PROGRESS_WAITING_LOGIN:
206 result = SIMBRIEF_RESULT_ERROR_NO_POPUP
207 else:
208 result = SIMBRIEF_RESULT_ERROR_OTHER
[685]209
[805]210 self._updateProgress(self._lastProgress, result, None)
211
212 return False
[685]213
[805]214 def _getResults(self, link):
215 """Get the result from the given link."""
216 availableInfo = {}
217 ## Holds analysis data to be used
218 flightInfo = {}
219
220 # Obtaining the xml
221 response = urllib2.urlopen(link)
222 xmlContent = response.read()
223 # Processing xml
224 content = etree.iterparse(StringIO(xmlContent))
[685]225
[805]226 for (action, element) in content:
227 # Processing tags that occur multiple times
228 if element.tag == "weather":
229 weatherElementList = list(element)
230 for weatherElement in weatherElementList:
231 flightInfo[weatherElement.tag] = weatherElement.text
232 else:
233 availableInfo[element.tag] = element.text
[685]234
[805]235 # Processing plan_html
236 ## Obtaining chart links
237 imageLinks = []
238 for imageLinkElement in lxml.html.find_class(availableInfo["plan_html"],
239 "ofpmaplink"):
240 for imageLink in imageLinkElement.iterlinks():
241 if imageLink[1] == 'src':
242 imageLinks.append(imageLink[2])
243 flightInfo["image_links"] = imageLinks
244 print(sorted(availableInfo.keys()))
245 htmlFilePath = "simbrief_plan.html" if self._htmlFilePath is None \
246 else self._htmlFilePath
247 with open(htmlFilePath, 'w') as f:
248 f.write(availableInfo["plan_html"])
249
250 gobject.idle_add(self._resultsAvailable, flightInfo)
[682]251
252#------------------------------------------------------------------------------
253
[805]254def initialize(initializedCallback):
[648]255 """Initialize the Chrome Embedded Framework."""
[805]256 global _toQuit, _simBriefHandler
[648]257 _toQuit = False
258
259 gobject.threads_init()
260
[805]261 _simBriefHandler = SimBriefHandler()
262 _initializeCEF([], initializedCallback)
[682]263
264#------------------------------------------------------------------------------
265
266def _initializeCEF(args, initializedCallback):
267 """Perform the actual initialization of CEF using the given arguments."""
268 print "Initializing CEF with args:", args
269
[648]270 settings = {
271 "debug": True, # cefpython debug messages in console and in log_file
272 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
273 "log_file": "", # Set to "" to disable
274 "release_dcheck_enabled": True, # Enable only when debugging
275 # This directories must be set on Linux
276 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
277 "resources_dir_path": cefpython.GetModuleDirectory(),
278 "browser_subprocess_path": "%s/%s" % \
279 (cefpython.GetModuleDirectory(), "subprocess"),
280 }
281
[682]282 switches={}
283 for arg in args:
284 if arg.startswith("--"):
285 if arg != "--enable-logging":
286 assignIndex = arg.find("=")
287 if assignIndex<0:
288 switches[arg[2:]] = ""
289 else:
290 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
291 else:
292 print "Unhandled switch", arg
293
294 cefpython.Initialize(settings, switches)
[648]295
296 gobject.timeout_add(10, _handleTimeout)
297
[682]298 print "Initialized, executing callback..."
299 initializedCallback()
300
[648]301#------------------------------------------------------------------------------
302
303def getContainer():
304 """Get a container object suitable for running a browser instance
305 within."""
306 if os.name=="nt":
307 container = gtk.DrawingArea()
308 container.set_property("can-focus", True)
309 container.connect("size-allocate", _handleSizeAllocate)
310 else:
[913]311 container = gtk.DrawingArea()
[648]312
313 container.show()
314
315 return container
316
317#------------------------------------------------------------------------------
318
319def startInContainer(container, url, browserSettings = {}):
320 """Start a browser instance in the given container with the given URL."""
321 if os.name=="nt":
[805]322 window = container.get_window()
323 if window is None:
324 print "mlx.gui.cef.startInContainer: no window found!"
325 windowID = None
326 else:
327 windowID = window.handle
[648]328 else:
[913]329 windowID = container.window.xid
[648]330
331 windowInfo = cefpython.WindowInfo()
[805]332 if windowID is not None:
333 windowInfo.SetAsChild(windowID)
[648]334
335 return cefpython.CreateBrowserSync(windowInfo,
336 browserSettings = browserSettings,
337 navigateUrl = url)
338
339#------------------------------------------------------------------------------
340
[685]341class OffscreenRenderHandler(object):
342 def GetRootScreenRect(self, browser, rect):
343 #print "GetRootScreenRect"
344 rect += [0, 0, 800, 600]
345 return True
346
347 def GetViewRect(self, browser, rect):
348 #print "GetViewRect"
349 rect += [0, 0, 800, 600]
350 return True
351
352 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
353 #print "GetScreenPoint", viewX, viewY
354 rect += [viewX, viewY]
355 return True
356
357 def GetScreenInfo(self, browser, screenInfo):
358 #print "GetScreenInfo"
359 pass
360
361 def OnPopupShow(self, browser, show):
362 #print "OnPopupShow", show
363 pass
364
365 def OnPopupSize(self, browser, rect):
366 #print "OnPopupSize", rect
367 pass
368
369 def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
370 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
371 pass
372
373 def OnCursorChange(self, browser, cursor):
374 #print "OnCursorChange", cursor
375 pass
376
377 def OnScrollOffsetChanged(self, browser):
378 #print "OnScrollOffsetChange"
379 pass
380
381 def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
382 popupFeatures, windowInfo, client, browserSettings,
383 noJavascriptAccess):
384 wInfo = cefpython.WindowInfo()
385 wInfo.SetAsOffscreen(int(0))
386
387 windowInfo.append(wInfo)
388
389 return False
390
391#------------------------------------------------------------------------------
392
393def initializeSimBrief():
394 """Initialize the (hidden) browser window for SimBrief."""
[805]395 _simBriefHandler.initialize()
[685]396
397#------------------------------------------------------------------------------
398
399def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
[805]400 """Call SimBrief with the given plan.
401
402 The callbacks will be called in the main GUI thread."""
403 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
[685]404
405#------------------------------------------------------------------------------
406
[648]407def finalize():
408 """Finalize the Chrome Embedded Framework."""
[805]409 global _toQuit
410 _toQuit = True
[702]411 cefpython.Shutdown()
[648]412
413#------------------------------------------------------------------------------
414
415def _handleTimeout():
416 """Handle the timeout by running the CEF message loop."""
417 if _toQuit:
418 return False
419 else:
420 cefpython.MessageLoopWork()
421 return True
422
423#------------------------------------------------------------------------------
424
425def _handleSizeAllocate(widget, sizeAlloc):
426 """Handle the size-allocate event."""
[805]427 window = widget.get_window()
428 if widget is not None:
429 cefpython.WindowUtils.OnSize(window.handle, 0, 0, 0)
Note: See TracBrowser for help on using the repository browser.