source: src/mlx/gui/cef.py@ 1088:2f55d163f603

python3
Last change on this file since 1088:2f55d163f603 was 1084:4d1bce6fff23, checked in by István Váradi <ivaradi@…>, 21 months ago

SimBrief handling via CEF is reinstated optionally

File size: 13.4 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 _formURLBase = MAVA_BASE_URL + "/simbrief_form.php"
60
61 _resultURLBase = MAVA_BASE_URL + "/simbrief_briefing.php"
62
63 @staticmethod
64 def getFormURL(plan):
65 """Get the form URL for the given plan."""
66 return SimBriefHandler._formURLBase + "?" + \
67 urllib.parse.urlencode(plan)
68
69 _querySettings = {
70 'navlog': True,
71 'etops': True,
72 'stepclimbs': True,
73 'tlr': True,
74 'notams': True,
75 'firnot': True,
76 'maps': 'Simple',
77 };
78
79
80 def __init__(self):
81 """Construct the handler."""
82 self._browser = None
83 self._plan = None
84 self._getCredentials = None
85 self._getCredentialsCount = 0
86 self._updateProgressFn = None
87 self._htmlFilePath = None
88 self._lastProgress = SIMBRIEF_PROGRESS_SEARCHING_BROWSER
89 self._timeoutID = None
90
91 def initialize(self):
92 """Create and initialize the browser used for Simbrief."""
93 windowInfo = cefpython.WindowInfo()
94 windowInfo.SetAsOffscreen(int(0))
95
96 self._browser = \
97 cefpython.CreateBrowserSync(windowInfo, browserSettings = {})
98 self._browser.SetClientHandler(OffscreenRenderHandler())
99 self._browser.SetFocus(True)
100 self._browser.SetClientCallback("OnLoadEnd", self._onLoadEnd)
101 self._browser.SetClientCallback("OnLoadError", self._onLoadError)
102 self._browser.SetClientCallback("OnBeforeBrowse",
103 lambda browser, frame, request,
104 user_gesture, is_redirect: False)
105
106 def call(self, plan, getCredentials, updateProgress, htmlFilePath):
107 """Call SimBrief with the given plan."""
108 self._timeoutID = GObject.timeout_add(120*1000, self._timedOut)
109
110 self._plan = plan
111 self._getCredentials = getCredentials
112 self._getCredentialsCount = 0
113 self._updateProgressFn = updateProgress
114 self._htmlFilePath = htmlFilePath
115
116 self._browser.LoadUrl(SimBriefHandler.getFormURL(plan))
117 self._updateProgress(SIMBRIEF_PROGRESS_LOADING_FORM,
118 SIMBRIEF_RESULT_NONE, None)
119
120 def finalize(self):
121 """Close the browser and release it."""
122 self._browser.CloseBrowser()
123 self._browser = None
124
125 def _onLoadEnd(self, browser, frame, http_code):
126 """Called when a page has been loaded in the SimBrief browser."""
127 url = frame.GetUrl()
128 print("gui.cef.SimBriefHandler._onLoadEnd", http_code, url)
129 if http_code>=300:
130 self._updateProgress(self._lastProgress,
131 SIMBRIEF_RESULT_ERROR_OTHER, None)
132 elif url.startswith(SimBriefHandler._formURLBase):
133 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
134 SIMBRIEF_RESULT_NONE, None)
135 elif url.startswith("https://www.simbrief.com/system/login.api.sso.php"):
136 js = "document.getElementsByClassName(\"login_option navigraph\")[0].click();"
137 frame.ExecuteJavascript(js)
138 elif url.startswith("https://identity.api.navigraph.com/login?"):
139 (user, password) = self._getCredentials(self._getCredentialsCount)
140 if user is None or password is None:
141 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_LOGIN,
142 SIMBRIEF_RESULT_ERROR_LOGIN_FAILED, None)
143 return
144
145 self._getCredentialsCount += 1
146
147 js = "form=document.getElementsByName(\"form\")[0];"
148 js +="form.username.value=\"" + user + "\";"
149 js +="form.password.value=\"" + password + "\";"
150 js +="form.submit();"
151 frame.ExecuteJavascript(js)
152 elif url.startswith("https://www.simbrief.com/ofp/ofp.loader.api.php"):
153 self._updateProgress(SIMBRIEF_PROGRESS_WAITING_RESULT,
154 SIMBRIEF_RESULT_NONE, None)
155 elif url.startswith(SimBriefHandler._resultURLBase):
156 self._updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
157 SIMBRIEF_RESULT_OK, None)
158
159 def _onLoadError(self, browser, frame, error_code, error_text_out,
160 failed_url):
161 """Called when loading of an URL fails."""
162 print("gui.cef.SimBriefHandler._onLoadError", browser, frame, error_code, error_text_out, failed_url)
163 self._updateProgress(self._lastProgress,
164 SIMBRIEF_RESULT_ERROR_OTHER, None)
165
166 def _updateProgress(self, progress, results, flightInfo):
167 """Update the progress."""
168 self._lastProgress = progress
169 if results!=SIMBRIEF_RESULT_NONE:
170 if self._timeoutID is not None:
171 GObject.source_remove(self._timeoutID)
172 self._plan = None
173
174 if self._updateProgressFn is not None:
175 self._updateProgressFn(progress, results, flightInfo)
176
177 def _timedOut(self):
178 """Called when the timeout occurs."""
179 if self._lastProgress==SIMBRIEF_PROGRESS_LOADING_FORM:
180 result = SIMBRIEF_RESULT_ERROR_NO_FORM
181 elif self._lastProgress==SIMBRIEF_PROGRESS_WAITING_LOGIN:
182 result = SIMBRIEF_RESULT_ERROR_NO_POPUP
183 else:
184 result = SIMBRIEF_RESULT_ERROR_OTHER
185
186 self._updateProgress(self._lastProgress, result, None)
187
188 return False
189
190#------------------------------------------------------------------------------
191
192def initialize(initializedCallback):
193 """Initialize the Chrome Embedded Framework."""
194 global _toQuit, _simBriefHandler
195 _toQuit = False
196
197 GObject.threads_init()
198
199 _simBriefHandler = SimBriefHandler()
200 _initializeCEF([], initializedCallback)
201
202#------------------------------------------------------------------------------
203
204def _initializeCEF(args, initializedCallback):
205 """Perform the actual initialization of CEF using the given arguments."""
206 print("Initializing CEF with args:", args)
207
208 settings = {
209 "debug": True, # cefpython debug messages in console and in log_file
210 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
211 "log_file": "", # Set to "" to disable
212 "release_dcheck_enabled": True, # Enable only when debugging
213 # This directories must be set on Linux
214 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
215 "resources_dir_path": cefpython.GetModuleDirectory(),
216 "browser_subprocess_path": "%s/%s" % \
217 (cefpython.GetModuleDirectory(), "subprocess"),
218 "windowless_rendering_enabled": True,
219 "cache_path": os.path.join(GLib.get_user_cache_dir(),
220 "mlxcef")
221 }
222
223 switches={}
224 for arg in args:
225 if arg.startswith("--"):
226 if arg != "--enable-logging":
227 assignIndex = arg.find("=")
228 if assignIndex<0:
229 switches[arg[2:]] = ""
230 else:
231 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
232 else:
233 print("Unhandled switch", arg)
234
235 cefpython.Initialize(settings, switches)
236
237 GObject.timeout_add(10, _handleTimeout)
238
239 print("Initialized, executing callback...")
240 initializedCallback()
241
242#------------------------------------------------------------------------------
243
244def getContainer():
245 """Get a container object suitable for running a browser instance
246 within."""
247 container = Gtk.DrawingArea()
248 container.set_property("can-focus", True)
249 container.connect("size-allocate", _handleSizeAllocate)
250 container.show()
251
252 return container
253
254#------------------------------------------------------------------------------
255
256def startInContainer(container, url, browserSettings = {}):
257 """Start a browser instance in the given container with the given URL."""
258 if os.name=="nt":
259 Gdk.threads_enter()
260 ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
261 ctypes.pythonapi.PyCapsule_GetPointer.argtypes = \
262 [ctypes.py_object]
263 gpointer = ctypes.pythonapi.PyCapsule_GetPointer(
264 container.get_property("window").__gpointer__, None)
265 libgdk = ctypes.CDLL("libgdk-3-0.dll")
266 windowID = libgdk.gdk_win32_window_get_handle(gpointer)
267 container.windowID = windowID
268 Gdk.threads_leave()
269 windowRect = [0, 0, 1, 1]
270 else:
271 container.set_visual(container.get_screen().lookup_visual(0x21))
272 windowID = container.get_window().get_xid()
273 windowRect = None
274
275 windowInfo = cefpython.WindowInfo()
276 if windowID is not None:
277 windowInfo.SetAsChild(windowID, windowRect = windowRect)
278
279 browser = cefpython.CreateBrowserSync(windowInfo,
280 browserSettings = browserSettings,
281 navigateUrl = url)
282 container.browser = browser
283
284 return browser
285
286#------------------------------------------------------------------------------
287
288class OffscreenRenderHandler(object):
289 def GetRootScreenRect(self, browser, rect_out):
290 #print "GetRootScreenRect"
291 rect_out += [0, 0, 1920, 1080]
292 return True
293
294 def GetViewRect(self, browser, rect_out):
295 #print "GetViewRect"
296 rect_out += [0, 40, 1920, 1040]
297 return True
298
299 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
300 #print "GetScreenPoint", viewX, viewY
301 rect += [viewX, viewY]
302 return True
303
304 def GetScreenInfo(self, browser, screenInfo):
305 #print "GetScreenInfo"
306 pass
307
308 def OnPopupShow(self, browser, show):
309 #print "OnPopupShow", show
310 pass
311
312 def OnPopupSize(self, browser, rect):
313 #print "OnPopupSize", rect
314 pass
315
316 def OnPaint(self, browser, element_type, dirty_rects, paint_buffer, width, height):
317 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
318 pass
319
320 def OnCursorChange(self, browser, cursor):
321 #print "OnCursorChange", cursor
322 pass
323
324 def OnScrollOffsetChanged(self, browser):
325 #print "OnScrollOffsetChange"
326 pass
327
328 def OnBeforePopup(self, browser, frame, target_url, target_frame_name,
329 target_disposition, user_gesture,
330 popup_features, window_info_out, client,
331 browser_settings_out, no_javascript_access_out,
332 **kwargs):
333 wInfo = cefpython.WindowInfo()
334 wInfo.SetAsOffscreen(int(0))
335
336 window_info_out.append(wInfo)
337
338 return False
339
340#------------------------------------------------------------------------------
341
342def initializeSimBrief():
343 """Initialize the (hidden) browser window for SimBrief."""
344 _simBriefHandler.initialize()
345
346#------------------------------------------------------------------------------
347
348def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
349 """Call SimBrief with the given plan.
350
351 The callbacks will be called in the main GUI thread."""
352 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
353
354#------------------------------------------------------------------------------
355
356def finalize():
357 """Finalize the Chrome Embedded Framework."""
358 global _toQuit
359 _toQuit = True
360
361 _simBriefHandler.finalize()
362
363 cefpython.Shutdown()
364
365#------------------------------------------------------------------------------
366
367def _handleTimeout():
368 """Handle the timeout by running the CEF message loop."""
369 if _toQuit:
370 return False
371 else:
372 cefpython.MessageLoopWork()
373 return True
374
375#------------------------------------------------------------------------------
376
377def _handleSizeAllocate(widget, sizeAlloc):
378 """Handle the size-allocate event on Windows."""
379 if os.name=="nt":
380 if widget is not None and hasattr(widget, "windowID"):
381 cefpython.WindowUtils.OnSize(widget.windowID, 0, 0, 0)
382 else:
383 if widget is not None and hasattr(widget, "browser") and \
384 widget.browser is not None:
385 widget.browser.SetBounds(sizeAlloc.x, sizeAlloc.y,
386 sizeAlloc.width, sizeAlloc.height)
Note: See TracBrowser for help on using the repository browser.