source: src/mlx/gui/cef.py@ 919:2ce8ca39525b

python3
Last change on this file since 919:2ce8ca39525b was 919:2ce8ca39525b, checked in by István Váradi <ivaradi@…>, 4 years ago

Ran 2to3

File size: 15.0 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, 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)
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 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
209
210 self._updateProgress(self._lastProgress, result, None)
211
212 return False
213
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 = urllib.request.urlopen(link)
222 xmlContent = response.read()
223 # Processing xml
224 content = etree.iterparse(StringIO(xmlContent))
225
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
234
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)
251
252#------------------------------------------------------------------------------
253
254def initialize(initializedCallback):
255 """Initialize the Chrome Embedded Framework."""
256 global _toQuit, _simBriefHandler
257 _toQuit = False
258
259 gobject.threads_init()
260
261 _simBriefHandler = SimBriefHandler()
262 _initializeCEF([], initializedCallback)
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
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
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)
295
296 gobject.timeout_add(10, _handleTimeout)
297
298 print("Initialized, executing callback...")
299 initializedCallback()
300
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:
311 container = gtk.DrawingArea()
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":
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
328 else:
329 windowID = container.window.xid
330
331 windowInfo = cefpython.WindowInfo()
332 if windowID is not None:
333 windowInfo.SetAsChild(windowID)
334
335 return cefpython.CreateBrowserSync(windowInfo,
336 browserSettings = browserSettings,
337 navigateUrl = url)
338
339#------------------------------------------------------------------------------
340
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."""
395 _simBriefHandler.initialize()
396
397#------------------------------------------------------------------------------
398
399def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
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)
404
405#------------------------------------------------------------------------------
406
407def finalize():
408 """Finalize the Chrome Embedded Framework."""
409 global _toQuit
410 _toQuit = True
411 cefpython.Shutdown()
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."""
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.