source: src/mlx/gui/cef.py@ 1095:6729940c3d74

python3
Last change on this file since 1095:6729940c3d74 was 1095:6729940c3d74, checked in by István Váradi <ivaradi@…>, 10 months ago

On SimBrief login failure, it is restarted.

File size: 14.1 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 if error_code==-3 and \
164 failed_url.startswith("https://identity.api.navigraph.com/connect/authorize"):
165 self._browser.LoadUrl(SimBriefHandler.getFormURL(self._plan))
166 self._updateProgress(SIMBRIEF_PROGRESS_LOADING_FORM,
167 SIMBRIEF_RESULT_NONE, None)
168 else:
169 self._updateProgress(self._lastProgress,
170 SIMBRIEF_RESULT_ERROR_OTHER, None)
171
172 def _updateProgress(self, progress, results, flightInfo):
173 """Update the progress."""
174 self._lastProgress = progress
175 if results!=SIMBRIEF_RESULT_NONE:
176 if self._timeoutID is not None:
177 GObject.source_remove(self._timeoutID)
178 self._plan = None
179
180 if self._updateProgressFn is not None:
181 self._updateProgressFn(progress, results, flightInfo)
182
183 def _timedOut(self):
184 """Called when the timeout occurs."""
185 if self._lastProgress==SIMBRIEF_PROGRESS_LOADING_FORM:
186 result = SIMBRIEF_RESULT_ERROR_NO_FORM
187 elif self._lastProgress==SIMBRIEF_PROGRESS_WAITING_LOGIN:
188 result = SIMBRIEF_RESULT_ERROR_NO_POPUP
189 else:
190 result = SIMBRIEF_RESULT_ERROR_OTHER
191
192 self._updateProgress(self._lastProgress, result, None)
193
194 return False
195
196#------------------------------------------------------------------------------
197
198def initialize(initializedCallback):
199 """Initialize the Chrome Embedded Framework."""
200 global _toQuit, _simBriefHandler
201 _toQuit = False
202
203 GObject.threads_init()
204
205 _simBriefHandler = SimBriefHandler()
206 _initializeCEF([], initializedCallback)
207
208#------------------------------------------------------------------------------
209
210def _initializeCEF(args, initializedCallback):
211 """Perform the actual initialization of CEF using the given arguments."""
212 if os.name=="nt":
213 _initializeCEF1(args, initializedCallback)
214 else:
215 GObject.timeout_add(100, _initializeCEF1, args, initializedCallback)
216
217#------------------------------------------------------------------------------
218
219def _initializeCEF1(args, initializedCallback):
220 """Perform the actual initialization of CEF using the given arguments."""
221 print("Initializing CEF with args:", args)
222
223 settings = {
224 "debug": True, # cefpython debug messages in console and in log_file
225 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
226 "log_file": "", # Set to "" to disable
227 "release_dcheck_enabled": True, # Enable only when debugging
228 # This directories must be set on Linux
229 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
230 "resources_dir_path": cefpython.GetModuleDirectory(),
231 "browser_subprocess_path": "%s/%s" % \
232 (cefpython.GetModuleDirectory(), "subprocess"),
233 "windowless_rendering_enabled": True,
234 "cache_path": os.path.join(GLib.get_user_cache_dir(),
235 "mlxcef")
236 }
237
238 switches={}
239 for arg in args:
240 if arg.startswith("--"):
241 if arg != "--enable-logging":
242 assignIndex = arg.find("=")
243 if assignIndex<0:
244 switches[arg[2:]] = ""
245 else:
246 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
247 else:
248 print("Unhandled switch", arg)
249
250 cefpython.Initialize(settings, switches)
251
252 GObject.timeout_add(10, _handleTimeout)
253
254 print("Initialized, executing callback...")
255 initializedCallback()
256 return False
257
258#------------------------------------------------------------------------------
259
260def getContainer():
261 """Get a container object suitable for running a browser instance
262 within."""
263 container = Gtk.DrawingArea()
264 container.set_property("can-focus", True)
265 container.connect("size-allocate", _handleSizeAllocate)
266 container.show()
267
268 return container
269
270#------------------------------------------------------------------------------
271
272def startInContainer(container, url, browserSettings = {}):
273 """Start a browser instance in the given container with the given URL."""
274 if os.name=="nt":
275 Gdk.threads_enter()
276 ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
277 ctypes.pythonapi.PyCapsule_GetPointer.argtypes = \
278 [ctypes.py_object]
279 gpointer = ctypes.pythonapi.PyCapsule_GetPointer(
280 container.get_property("window").__gpointer__, None)
281 libgdk = ctypes.CDLL("libgdk-3-0.dll")
282 windowID = libgdk.gdk_win32_window_get_handle(gpointer)
283 container.windowID = windowID
284 Gdk.threads_leave()
285 windowRect = [0, 0, 1, 1]
286 else:
287 container.set_visual(container.get_screen().lookup_visual(0x21))
288 windowID = container.get_window().get_xid()
289 windowRect = None
290
291 windowInfo = cefpython.WindowInfo()
292 if windowID is not None:
293 windowInfo.SetAsChild(windowID, windowRect = windowRect)
294
295 browser = cefpython.CreateBrowserSync(windowInfo,
296 browserSettings = browserSettings,
297 navigateUrl = url)
298 container.browser = browser
299
300 return browser
301
302#------------------------------------------------------------------------------
303
304class OffscreenRenderHandler(object):
305 def GetRootScreenRect(self, browser, rect_out):
306 #print "GetRootScreenRect"
307 rect_out += [0, 0, 1920, 1080]
308 return True
309
310 def GetViewRect(self, browser, rect_out):
311 #print "GetViewRect"
312 rect_out += [0, 40, 1920, 1040]
313 return True
314
315 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
316 #print "GetScreenPoint", viewX, viewY
317 rect += [viewX, viewY]
318 return True
319
320 def GetScreenInfo(self, browser, screenInfo):
321 #print "GetScreenInfo"
322 pass
323
324 def OnPopupShow(self, browser, show):
325 #print "OnPopupShow", show
326 pass
327
328 def OnPopupSize(self, browser, rect):
329 #print "OnPopupSize", rect
330 pass
331
332 def OnPaint(self, browser, element_type, dirty_rects, paint_buffer, width, height):
333 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
334 pass
335
336 def OnCursorChange(self, browser, cursor):
337 #print "OnCursorChange", cursor
338 pass
339
340 def OnScrollOffsetChanged(self, browser):
341 #print "OnScrollOffsetChange"
342 pass
343
344 def OnBeforePopup(self, browser, frame, target_url, target_frame_name,
345 target_disposition, user_gesture,
346 popup_features, window_info_out, client,
347 browser_settings_out, no_javascript_access_out,
348 **kwargs):
349 wInfo = cefpython.WindowInfo()
350 wInfo.SetAsOffscreen(int(0))
351
352 window_info_out.append(wInfo)
353
354 return False
355
356#------------------------------------------------------------------------------
357
358def initializeSimBrief():
359 """Initialize the (hidden) browser window for SimBrief."""
360 _simBriefHandler.initialize()
361
362#------------------------------------------------------------------------------
363
364def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
365 """Call SimBrief with the given plan.
366
367 The callbacks will be called in the main GUI thread."""
368 _simBriefHandler.call(plan, getCredentials, updateProgress, htmlFilePath)
369
370#------------------------------------------------------------------------------
371
372def finalize():
373 """Finalize the Chrome Embedded Framework."""
374 global _toQuit
375 _toQuit = True
376
377 _simBriefHandler.finalize()
378
379 cefpython.Shutdown()
380
381#------------------------------------------------------------------------------
382
383def _handleTimeout():
384 """Handle the timeout by running the CEF message loop."""
385 if _toQuit:
386 return False
387 else:
388 cefpython.MessageLoopWork()
389 return True
390
391#------------------------------------------------------------------------------
392
393def _handleSizeAllocate(widget, sizeAlloc):
394 """Handle the size-allocate event on Windows."""
395 if os.name=="nt":
396 if widget is not None and hasattr(widget, "windowID"):
397 cefpython.WindowUtils.OnSize(widget.windowID, 0, 0, 0)
398 else:
399 if widget is not None and hasattr(widget, "browser") and \
400 widget.browser is not None:
401 widget.browser.SetBounds(sizeAlloc.x, sizeAlloc.y,
402 sizeAlloc.width, sizeAlloc.height)
Note: See TracBrowser for help on using the repository browser.