source: src/mlx/gui/cef.py@ 1096:3b923185a50d

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

The CEF cache directory is removed on startup

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