source: src/mlx/gui/cef.py@ 1083:a5f219c25f2a

python3
Last change on this file since 1083:a5f219c25f2a was 1083:a5f219c25f2a, checked in by István Váradi <ivaradi@…>, 13 months ago

Updates for CEF 108 and the corresponding CEFPython.

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