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
Line 
1from common import *
2
3from mava_simbrief import MavaSimbriefIntegrator
4
5from mlx.util import secondaryInstallation
6
7from cefpython3 import cefpython
8from selenium import webdriver
9from selenium.webdriver.chrome.options import Options
10
11import platform
12import json
13import time
14import os
15import re
16import threading
17import tempfile
18import traceback
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
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
77#------------------------------------------------------------------------------
78
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
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
110 self._driver = None
111
112 self._simBriefBrowser = None
113
114 self._toQuit = False
115
116 @property
117 def programDirectory(self):
118 """Get the program directory."""
119 return self._programDirectory
120
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
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
139 driver = self._driver = webdriver.Chrome(chrome_options = options)
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
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
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
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
190 def _callSimBrief(self, plan, driver,
191 getCredentials, updateProgress, htmlFilePath):
192 """Perform the SimBrief call."""
193 self._simBriefBrowser.LoadUrl(self.simBriefInitURL)
194
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
200 if link is not None:
201 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
202 SIMBRIEF_RESULT_NONE, None)
203
204 try:
205 flight_info = integrator.get_results(link,
206 html_file_path =
207 htmlFilePath)
208
209 updateProgress(SIMBRIEF_PROGRESS_DONE,
210 SIMBRIEF_RESULT_OK, flight_info)
211 except Exception, e:
212 print "Failed to retrieve the briefing:", e
213 updateProgress(SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING,
214 SIMBRIEF_RESULT_ERROR_OTHER, None)
215
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):
224 """Initialize the Chrome Embedded Framework."""
225 global _toQuit, _seleniumHandler
226 _toQuit = False
227
228 gobject.threads_init()
229
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
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
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)
272
273 gobject.timeout_add(10, _handleTimeout)
274
275 print "Initialized, executing callback..."
276 initializedCallback()
277
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
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."""
368 _seleniumHandler.initializeSimBrief()
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
379def finalize():
380 """Finalize the Chrome Embedded Framework."""
381 global _toQuit, _seleniumHandler
382 toQuit = True
383 cefpython.Shutdown()
384 _seleniumHandler.quit()
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.