source: src/mlx/gui/cef.py@ 702:b76cdc5ff2dc

cef
Last change on this file since 702:b76cdc5ff2dc was 702:b76cdc5ff2dc, checked in by István Váradi <ivaradi@…>, 7 years ago

CEF is shut down before the Selenium handler so that quitting works even while
performing some Selenium-driver operation (re #279).

File size: 13.4 KB
RevLine 
[648]1from common import *
2
[685]3from mava_simbrief import MavaSimbriefIntegrator
4
[682]5from mlx.util import secondaryInstallation
6
7from cefpython3 import cefpython
8from selenium import webdriver
9from selenium.webdriver.chrome.options import Options
10
[648]11import platform
12import json
[682]13import time
[648]14import os
15import re
[682]16import threading
17import tempfile
18import traceback
[648]19
20#------------------------------------------------------------------------------
21
22## @package mlx.gui.cef
23#
24# Some helper stuff related to the Chrome Embedded Framework
25
26#------------------------------------------------------------------------------
27
28# Indicate if we should quit
29_toQuit = False
30
[682]31# The Selenium thread
32_seleniumHandler = None
33
34#------------------------------------------------------------------------------
35
36def getArgsFilePath():
37 """Get the path of the argument file."""
38 if os.name=="nt":
39 return os.path.join(tempfile.gettempdir(),
40 "mlxcef.args" +
41 (".secondary" if secondaryInstallation else ""))
42 else:
43 import pwd
44 return os.path.join(tempfile.gettempdir(),
45 "mlxcef." + pwd.getpwuid(os.getuid())[0] + ".args" +
46 (".secondary" if secondaryInstallation else ""))
47
48#------------------------------------------------------------------------------
49
50class ArgsFileWaiter(threading.Thread):
51 """A thread to wait for the appearance of the arguments file."""
52 def __init__(self, initializedCallback):
53 """Construct the thread."""
54 threading.Thread.__init__(self)
55 self.daemon = True
56
57 self._initializedCallback = initializedCallback
58
59 def run(self):
60 """Repeatedly check for the existence of the arguments file.
61
62 If it is found, read it, extract the arguments and insert a job into
63 the GUI loop to perform the actual initialization of CEF."""
64 argsFilePath = getArgsFilePath()
65 print "Waiting for the arguments file '%s' to appear" % (argsFilePath,)
66
67 while not os.path.exists(argsFilePath):
68 time.sleep(0.1)
69
70 print "Got arguments, reading them."""
71
72 with open(argsFilePath, "rt") as f:
73 args = f.read().split()
74
75 gobject.idle_add(_initializeCEF, args, self._initializedCallback)
76
[648]77#------------------------------------------------------------------------------
78
[692]79SIMBRIEF_PROGRESS_SEARCHING_BROWSER = MavaSimbriefIntegrator.PROGRESS_SEARCHING_BROWSER
80SIMBRIEF_PROGRESS_LOADING_FORM = MavaSimbriefIntegrator.PROGRESS_LOADING_FORM
81SIMBRIEF_PROGRESS_FILLING_FORM = MavaSimbriefIntegrator.PROGRESS_FILLING_FORM
82SIMBRIEF_PROGRESS_WAITING_LOGIN = MavaSimbriefIntegrator.PROGRESS_WAITING_LOGIN
83SIMBRIEF_PROGRESS_LOGGING_IN = MavaSimbriefIntegrator.PROGRESS_LOGGING_IN
84SIMBRIEF_PROGRESS_WAITING_RESULT = MavaSimbriefIntegrator.PROGRESS_WAITING_RESULT
85
86SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING = MavaSimbriefIntegrator.PROGRESS_MAX + 1
87SIMBRIEF_PROGRESS_DONE = 1000
88
89SIMBRIEF_RESULT_NONE = MavaSimbriefIntegrator.RESULT_NONE
90SIMBRIEF_RESULT_OK = MavaSimbriefIntegrator.RESULT_OK
91SIMBRIEF_RESULT_ERROR_OTHER = MavaSimbriefIntegrator.RESULT_ERROR_OTHER
92SIMBRIEF_RESULT_ERROR_NO_FORM = MavaSimbriefIntegrator.RESULT_ERROR_NO_FORM
93SIMBRIEF_RESULT_ERROR_NO_POPUP = MavaSimbriefIntegrator.RESULT_ERROR_NO_POPUP
94SIMBRIEF_RESULT_ERROR_LOGIN_FAILED = MavaSimbriefIntegrator.RESULT_ERROR_LOGIN_FAILED
95
96#------------------------------------------------------------------------------
97
[682]98class SeleniumHandler(threading.Thread):
99 """Thread to handle Selenium operations."""
100 def __init__(self, programDirectory):
101 """Construct the thread."""
102 threading.Thread.__init__(self)
103 self.daemon = False
104
105 self._programDirectory = programDirectory
106
107 self._commandsCondition = threading.Condition()
108 self._commands = []
109
[685]110 self._driver = None
111
[695]112 self._simBriefBrowser = None
113
[682]114 self._toQuit = False
115
[685]116 @property
117 def programDirectory(self):
118 """Get the program directory."""
119 return self._programDirectory
120
[695]121 @property
122 def simBriefInitURL(self):
123 """Get the initial URL for the SimBrief browser."""
124 return "file://" + os.path.join(self.programDirectory, "simbrief.html")
125
[682]126 def run(self):
127 """Create the Selenium driver and the perform any operations
128 requested."""
129 scriptName = "mlx_cef_caller"
130 if secondaryInstallation:
131 scriptName += "_secondary"
132 scriptName += ".bat" if os.name=="nt" else ".sh"
133
134 scriptPath = os.path.join(self._programDirectory, scriptName)
135 print "Creating the Selenium driver to call script", scriptPath
136
137 options = Options()
138 options.binary_location = scriptPath
[685]139 driver = self._driver = webdriver.Chrome(chrome_options = options)
[682]140 # try:
141 # except:
142 # traceback.print_exc()
143
144 print "Created Selenium driver."
145 while not self._toQuit:
146 with self._commandsCondition:
147 while not self._commands:
148 self._commandsCondition.wait()
149
150 command = self._commands[0]
151 del self._commands[0]
152
153 command()
154
155 driver.quit()
156
[695]157 def initializeSimBrief(self):
158 """Create and initialize the browser used for Simbrief."""
159 windowInfo = cefpython.WindowInfo()
160 windowInfo.SetAsOffscreen(int(0))
161
162 url = self.simBriefInitURL
163 self._simBriefBrowser = \
164 cefpython.CreateBrowserSync(windowInfo, browserSettings = {},
165 navigateUrl = self.simBriefInitURL)
166 self._simBriefBrowser.SetClientHandler(OffscreenRenderHandler())
167 self._simBriefBrowser.SetFocus(True)
168
[685]169 def callSimBrief(self, plan, getCredentials, updateProgress,
170 htmlFilePath):
171 """Call SimBrief with the given plan."""
172 self._enqueue(lambda:
173 self._callSimBrief(plan, self._driver,
174 getCredentials, updateProgress,
175 htmlFilePath))
176
[682]177 def quit(self):
178 """Instruct the thread to quit and then join it."""
179 self._enqueue(self._quit)
180 self.join()
181
182 def _enqueue(self, command):
183 """Enqueue the given command.
184
185 command should be a function to be executed in the thread."""
186 with self._commandsCondition:
187 self._commands.append(command)
188 self._commandsCondition.notify()
189
[685]190 def _callSimBrief(self, plan, driver,
191 getCredentials, updateProgress, htmlFilePath):
192 """Perform the SimBrief call."""
[696]193 self._simBriefBrowser.LoadUrl(self.simBriefInitURL)
194
[685]195 integrator = MavaSimbriefIntegrator(plan = plan, driver = driver)
196 link = integrator.get_xml_link(getCredentials, updateProgress,
197 local_xml_debug = False,
198 local_html_debug = False)
199
[692]200 if link is not None:
201 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
202 SIMBRIEF_RESULT_NONE, None)
[685]203
[692]204 try:
205 flight_info = integrator.get_results(link,
206 html_file_path =
207 htmlFilePath)
[685]208
[692]209 updateProgress(SIMBRIEF_PROGRESS_DONE,
210 SIMBRIEF_RESULT_OK, flight_info)
211 except Exception, e:
[697]212 print "Failed to retrieve the briefing:", e
[692]213 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
214 SIMBRIEF_RESULT_ERROR_OTHER, None)
[685]215
[682]216 def _quit(self):
217 """Set the _toQuit member variable to indicate that the thread should
218 quit."""
219 self._toQuit = True
220
221#------------------------------------------------------------------------------
222
223def initialize(programDirectory, initializedCallback):
[648]224 """Initialize the Chrome Embedded Framework."""
[682]225 global _toQuit, _seleniumHandler
[648]226 _toQuit = False
227
228 gobject.threads_init()
229
[682]230 argsFilePath = getArgsFilePath()
231 try:
232 os.unlink(argsFilePath)
233 except:
234 pass
235
236 _seleniumHandler = SeleniumHandler(programDirectory)
237 _seleniumHandler.start()
238
239 ArgsFileWaiter(initializedCallback).start()
240
241#------------------------------------------------------------------------------
242
243def _initializeCEF(args, initializedCallback):
244 """Perform the actual initialization of CEF using the given arguments."""
245 print "Initializing CEF with args:", args
246
[648]247 settings = {
248 "debug": True, # cefpython debug messages in console and in log_file
249 "log_severity": cefpython.LOGSEVERITY_VERBOSE, # LOGSEVERITY_VERBOSE
250 "log_file": "", # Set to "" to disable
251 "release_dcheck_enabled": True, # Enable only when debugging
252 # This directories must be set on Linux
253 "locales_dir_path": os.path.join(cefpython.GetModuleDirectory(), "locales"),
254 "resources_dir_path": cefpython.GetModuleDirectory(),
255 "browser_subprocess_path": "%s/%s" % \
256 (cefpython.GetModuleDirectory(), "subprocess"),
257 }
258
[682]259 switches={}
260 for arg in args:
261 if arg.startswith("--"):
262 if arg != "--enable-logging":
263 assignIndex = arg.find("=")
264 if assignIndex<0:
265 switches[arg[2:]] = ""
266 else:
267 switches[arg[2:assignIndex]] = arg[assignIndex+1:]
268 else:
269 print "Unhandled switch", arg
270
271 cefpython.Initialize(settings, switches)
[648]272
273 gobject.timeout_add(10, _handleTimeout)
274
[682]275 print "Initialized, executing callback..."
276 initializedCallback()
277
[648]278#------------------------------------------------------------------------------
279
280def getContainer():
281 """Get a container object suitable for running a browser instance
282 within."""
283 if os.name=="nt":
284 container = gtk.DrawingArea()
285 container.set_property("can-focus", True)
286 container.connect("size-allocate", _handleSizeAllocate)
287 else:
288 container = gtk.VBox(True, 0)
289
290 container.show()
291
292 return container
293
294#------------------------------------------------------------------------------
295
296def startInContainer(container, url, browserSettings = {}):
297 """Start a browser instance in the given container with the given URL."""
298 if os.name=="nt":
299 windowID = container.get_window().handle
300 else:
301 m = re.search("GtkVBox at 0x(\w+)", str(container))
302 hexID = m.group(1)
303 windowID = int(hexID, 16)
304
305 windowInfo = cefpython.WindowInfo()
306 windowInfo.SetAsChild(windowID)
307
308 return cefpython.CreateBrowserSync(windowInfo,
309 browserSettings = browserSettings,
310 navigateUrl = url)
311
312#------------------------------------------------------------------------------
313
[685]314class OffscreenRenderHandler(object):
315 def GetRootScreenRect(self, browser, rect):
316 #print "GetRootScreenRect"
317 rect += [0, 0, 800, 600]
318 return True
319
320 def GetViewRect(self, browser, rect):
321 #print "GetViewRect"
322 rect += [0, 0, 800, 600]
323 return True
324
325 def GetScreenPoint(self, browser, viewX, viewY, screenCoordinates):
326 #print "GetScreenPoint", viewX, viewY
327 rect += [viewX, viewY]
328 return True
329
330 def GetScreenInfo(self, browser, screenInfo):
331 #print "GetScreenInfo"
332 pass
333
334 def OnPopupShow(self, browser, show):
335 #print "OnPopupShow", show
336 pass
337
338 def OnPopupSize(self, browser, rect):
339 #print "OnPopupSize", rect
340 pass
341
342 def OnPaint(self, browser, paintElementType, dirtyRects, buffer, width, height):
343 #print "OnPaint", paintElementType, dirtyRects, buffer, width, height
344 pass
345
346 def OnCursorChange(self, browser, cursor):
347 #print "OnCursorChange", cursor
348 pass
349
350 def OnScrollOffsetChanged(self, browser):
351 #print "OnScrollOffsetChange"
352 pass
353
354 def OnBeforePopup(self, browser, frame, targetURL, targetFrameName,
355 popupFeatures, windowInfo, client, browserSettings,
356 noJavascriptAccess):
357 wInfo = cefpython.WindowInfo()
358 wInfo.SetAsOffscreen(int(0))
359
360 windowInfo.append(wInfo)
361
362 return False
363
364#------------------------------------------------------------------------------
365
366def initializeSimBrief():
367 """Initialize the (hidden) browser window for SimBrief."""
[695]368 _seleniumHandler.initializeSimBrief()
[685]369
370#------------------------------------------------------------------------------
371
372def callSimBrief(plan, getCredentials, updateProgress, htmlFilePath):
373 """Call SimBrief with the given plan."""
374 _seleniumHandler.callSimBrief(plan, getCredentials,
375 updateProgress, htmlFilePath)
376
377#------------------------------------------------------------------------------
378
[648]379def finalize():
380 """Finalize the Chrome Embedded Framework."""
[682]381 global _toQuit, _seleniumHandler
[648]382 toQuit = True
[702]383 cefpython.Shutdown()
[682]384 _seleniumHandler.quit()
[648]385
386#------------------------------------------------------------------------------
387
388def _handleTimeout():
389 """Handle the timeout by running the CEF message loop."""
390 if _toQuit:
391 return False
392 else:
393 cefpython.MessageLoopWork()
394 return True
395
396#------------------------------------------------------------------------------
397
398def _handleSizeAllocate(widget, sizeAlloc):
399 """Handle the size-allocate event."""
400 cefpython.WindowUtils.OnSize(widget.get_window().handle, 0, 0, 0)
Note: See TracBrowser for help on using the repository browser.