source: src/mlx/gui/cef.py@ 996:8035d80d5feb

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

Using 'Gtk' instead of 'gtk' (re #347)

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