source: src/mlx/gui/cef.py@ 924:05c2338a2dc4

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

Fixed CEF window ID detection for Linux (re #347).

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 container.set_visual(container.get_screen().lookup_visual(0x21))
330 windowID = container.get_window().get_xid()
331
332 windowInfo = cefpython.WindowInfo()
333 if windowID is not None:
334 windowInfo.SetAsChild(windowID)
335
336 return cefpython.CreateBrowserSync(windowInfo,
337 browserSettings = browserSettings,
338 navigateUrl = url)
339
340#------------------------------------------------------------------------------
341
342class OffscreenRenderHandler(object):
343 def GetRootScreenRect(self, browser, rect):
344 #print "GetRootScreenRect"
345 rect += [0, 0, 800, 600]
346 return True
347
348 def GetViewRect(self, browser, rect):
349 #print "GetViewRect"
350 rect += [0, 0, 800, 600]
351 return True
352
353 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
354 #print "GetScreenPoint", viewX, viewY
355 rect += [viewX, viewY]
356 return True
357
358 def GetScreenInfo(self, browser, screenInfo):
359 #print "GetScreenInfo"
360 pass
361
362 def OnPopupShow(self, browser, show):
363 #print "OnPopupShow", show
364 pass
365
366 def OnPopupSize(self, browser, rect):
367 #print "OnPopupSize", rect
368 pass
369
370 def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
371 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
372 pass
373
374 def OnCursorChange(self, browser, cursor):
375 #print "OnCursorChange", cursor
376 pass
377
378 def OnScrollOffsetChanged(self, browser):
379 #print "OnScrollOffsetChange"
380 pass
381
382 def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
383 popupFeatures, windowInfo, client, browserSettings,
384 noJavascriptAccess):
385 wInfo = cefpython.WindowInfo()
386 wInfo.SetAsOffscreen(int(0))
387
388 windowInfo.append(wInfo)
389
390 return False
391
392#------------------------------------------------------------------------------
393
394def initializeSimBrief():
395 """Initialize the (hidden) browser window for SimBrief."""
396 _simBriefHandler.initialize()
397
398#------------------------------------------------------------------------------
399
400def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
401 """Call SimBrief with the given plan.
402
403 The callbacks will be called in the main GUI thread."""
404 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
405
406#------------------------------------------------------------------------------
407
408def finalize():
409 """Finalize the Chrome Embedded Framework."""
410 global _toQuit
411 _toQuit = True
412 cefpython.Shutdown()
413
414#------------------------------------------------------------------------------
415
416def _handleTimeout():
417 """Handle the timeout by running the CEF message loop."""
418 if _toQuit:
419 return False
420 else:
421 cefpython.MessageLoopWork()
422 return True
423
424#------------------------------------------------------------------------------
425
426def _handleSizeAllocate(widget, sizeAlloc):
427 """Handle the size-allocate event."""
428 window = widget.get_window()
429 if widget is not None:
430 cefpython.WindowUtils.OnSize(window.handle, 0, 0, 0)
Note: See TracBrowser for help on using the repository browser.