source: src/mlx/gui/cef.py@ 1091:190a836956c0

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

CEF is initialized with a delay on Linux to avoid it crashing

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