Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RWebDisplayHandle.cxx
Go to the documentation of this file.
1 /// \file RWebDisplayHandle.cxx
2 /// \ingroup WebGui ROOT7
3 /// \author Sergey Linev <s.linev@gsi.de>
4 /// \date 2018-10-17
5 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6 /// is welcome!
7 
8 /*************************************************************************
9  * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
10  * All rights reserved. *
11  * *
12  * For the licensing terms see $ROOTSYS/LICENSE. *
13  * For the list of contributors see $ROOTSYS/README/CREDITS. *
14  *************************************************************************/
15 
17 
18 #include <ROOT/RMakeUnique.hxx>
19 #include <ROOT/RLogger.hxx>
20 
21 #include "RConfigure.h"
22 #include "TSystem.h"
23 #include "TRandom.h"
24 #include "TString.h"
25 #include "TObjArray.h"
26 #include "TEnv.h"
27 
28 #include <regex>
29 
30 #ifdef _MSC_VER
31 #include <process.h>
32 #else
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <signal.h>
36 #include <spawn.h>
37 #endif
38 
39 using namespace std::string_literals;
40 
41 //////////////////////////////////////////////////////////////////////////////////////////////////
42 /// Static holder of registered creators of web displays
43 
44 std::map<std::string, std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator>> &ROOT::Experimental::RWebDisplayHandle::GetMap()
45 {
46  static std::map<std::string, std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator>> sMap;
47  return sMap;
48 }
49 
50 //////////////////////////////////////////////////////////////////////////////////////////////////
51 /// Search for specific browser creator
52 /// If not found, try to add one
53 /// \param name - creator name like ChromeCreator
54 /// \param libname - shared library name where creator could be provided
55 
56 std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator> &ROOT::Experimental::RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
57 {
58  auto &m = GetMap();
59  auto search = m.find(name);
60  if (search == m.end()) {
61 
62  if (libname == "ChromeCreator") {
63  m.emplace(name, std::make_unique<ChromeCreator>());
64  } else if (libname == "FirefoxCreator") {
65  m.emplace(name, std::make_unique<FirefoxCreator>());
66  } else if (libname == "BrowserCreator") {
67  m.emplace(name, std::make_unique<BrowserCreator>(false));
68  } else if (!libname.empty()) {
69  gSystem->Load(libname.c_str());
70  }
71 
72  search = m.find(name); // try again
73  }
74 
75  if (search != m.end())
76  return search->second;
77 
78  static std::unique_ptr<ROOT::Experimental::RWebDisplayHandle::Creator> dummy;
79  return dummy;
80 }
81 
82 namespace ROOT {
83 namespace Experimental {
84 
85 //////////////////////////////////////////////////////////////////////////////////////////////////
86 /// Specialized handle to hold information about running browser process
87 /// Used to correctly cleanup all processes and temporary directories
88 
89 class RWebBrowserHandle : public RWebDisplayHandle {
90 
91 #ifdef _MSC_VER
92  typedef int browser_process_id;
93 #else
94  typedef pid_t browser_process_id;
95 #endif
96  std::string fTmpDir;
97  bool fHasPid{false};
98  browser_process_id fPid;
99 
100 public:
101  RWebBrowserHandle(const std::string &url, const std::string &tmpdir) : RWebDisplayHandle(url), fTmpDir(tmpdir) {}
102 
103  RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid)
104  : RWebDisplayHandle(url), fTmpDir(tmpdir), fHasPid(true), fPid(pid)
105  {
106  }
107 
108  virtual ~RWebBrowserHandle()
109  {
110 #ifdef _MSC_VER
111  if (fHasPid)
112  gSystem->Exec(("taskkill /F /PID "s + std::to_string(fPid)).c_str());
113  std::string rmdir = "rmdir /S /Q ";
114 #else
115  if (fHasPid)
116  kill(fPid, SIGKILL);
117  std::string rmdir = "rm -rf ";
118 #endif
119  if (!fTmpDir.empty())
120  gSystem->Exec((rmdir + fTmpDir).c_str());
121  }
122 };
123 
124 } // namespace Experimental
125 } // namespace ROOT
126 
127 //////////////////////////////////////////////////////////////////////////////////////////////////
128 /// Class to handle starting of web-browsers like Chrome or Firefox
129 
130 ROOT::Experimental::RWebDisplayHandle::BrowserCreator::BrowserCreator(bool custom, const std::string &exec)
131 {
132  if (custom) return;
133 
134  if (!exec.empty()) {
135  if (exec.find("$url") == std::string::npos) {
136  fProg = exec;
137 #ifdef _MSC_VER
138  fExec = exec + " $url";
139 #else
140  fExec = exec + " $url &";
141 #endif
142  } else {
143  fExec = exec;
144  auto pos = exec.find(" ");
145  if (pos != std::string::npos)
146  fProg = exec.substr(0, pos);
147  }
148  } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
149  fExec = "open \'$url\'";
150  } else if (gSystem->InheritsFrom("TWinNTSystem")) {
151  fExec = "start $url";
152  } else {
153  fExec = "xdg-open \'$url\' &";
154  }
155 }
156 
157 //////////////////////////////////////////////////////////////////////////////////////////////////
158 /// Check if browser executable exists and can be used
159 
160 void ROOT::Experimental::RWebDisplayHandle::BrowserCreator::TestProg(const std::string &nexttry, bool check_std_paths)
161 {
162  if (nexttry.empty() || !fProg.empty())
163  return;
164 
165  if (!gSystem->AccessPathName(nexttry.c_str(), kExecutePermission)) {
166 #ifdef R__MACOSX
167  fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
168 #else
169  fProg = nexttry;
170 #endif
171  return;
172  }
173 
174  if (!check_std_paths)
175  return;
176 
177 #ifdef _MSC_VER
178  std::string ProgramFiles = gSystem->Getenv("ProgramFiles");
179  auto pos = ProgramFiles.find(" (x86)");
180  if (pos != std::string::npos)
181  ProgramFiles.erase(pos, 6);
182  std::string ProgramFilesx86 = gSystem->Getenv("ProgramFiles(x86)");
183 
184  if (!ProgramFiles.empty())
185  TestProg(ProgramFiles + nexttry, false);
186  if (!ProgramFilesx86.empty())
187  TestProg(ProgramFilesx86 + nexttry, false);
188 #endif
189 }
190 
191 //////////////////////////////////////////////////////////////////////////////////////////////////
192 /// Display given URL in web browser
193 
194 std::unique_ptr<ROOT::Experimental::RWebDisplayHandle>
195 ROOT::Experimental::RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args)
196 {
197  std::string url = args.GetFullUrl();
198  if (url.empty())
199  return nullptr;
200 
201  std::string exec;
202  if (args.IsHeadless())
203  exec = fBatchExec;
204  else if (args.IsStandalone())
205  exec = fExec;
206  else
207 #ifdef _MSC_VER
208  exec = "$prog $url";
209 #else
210  exec = "$prog $url &";
211 #endif
212 
213  if (exec.empty())
214  return nullptr;
215 
216  std::string swidth = std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800),
217  sheight = std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600),
218  sposx = std::to_string(args.GetX() >= 0 ? args.GetX() : 0),
219  sposy = std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
220 
221  ProcessGeometry(exec, args);
222 
223  std::string rmdir = MakeProfile(exec, args.IsHeadless());
224 
225  exec = std::regex_replace(exec, std::regex("\\$url"), url);
226  exec = std::regex_replace(exec, std::regex("\\$width"), swidth);
227  exec = std::regex_replace(exec, std::regex("\\$height"), sheight);
228  exec = std::regex_replace(exec, std::regex("\\$posx"), sposx);
229  exec = std::regex_replace(exec, std::regex("\\$posy"), sposy);
230 
231  if (exec.compare(0,5,"fork:") == 0) {
232  if (fProg.empty()) {
233  R__ERROR_HERE("WebDisplay") << "Fork instruction without executable";
234  return nullptr;
235  }
236 
237  exec.erase(0, 5);
238 
239 #ifndef _MSC_VER
240 
241  std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
242  if (!fargs || (fargs->GetLast()<=0)) {
243  R__ERROR_HERE("WebDisplay") << "Fork instruction is empty";
244  return nullptr;
245  }
246 
247  std::vector<char *> argv;
248  argv.push_back((char *) fProg.c_str());
249  for (Int_t n = 0; n <= fargs->GetLast(); ++n)
250  argv.push_back((char *)fargs->At(n)->GetName());
251  argv.push_back(nullptr);
252 
253  R__DEBUG_HERE("WebDisplay") << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
254 
255  pid_t pid;
256  int status = posix_spawn(&pid, argv[0], nullptr, nullptr, argv.data(), nullptr);
257  if (status != 0) {
258  R__ERROR_HERE("WebDisplay") << "Fail to launch " << argv[0];
259  return 0;
260  }
261 
262  // add processid and rm dir
263 
264  return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
265 
266  // return win.AddProcId(batch_mode, key, std::string("pid:") + std::to_string((int)pid) + rmdir);
267 
268 #else
269  std::string tmp;
270  char c;
271  int pid;
272  if (!fProg.empty()) {
273  exec = "wmic process call create \""s + fProg + exec;
274  } else {
275  R__ERROR_HERE("WebDisplay") << "No Web browser found in Program Files!";
276  return nullptr;
277  }
278  exec.append("\" | find \"ProcessId\" ");
279  std::string process_id = gSystem->GetFromPipe(exec.c_str());
280  std::stringstream ss(process_id);
281  ss >> tmp >> c >> pid;
282 
283  // add processid and rm dir
284  return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
285 
286  //return win.AddProcId(batch_mode, key, std::string("pid:") + std::to_string((int)pid) + rmdir);
287 #endif
288  }
289 
290 #ifdef _MSC_VER
291  std::vector<char *> argv;
292  std::string firstarg = fProg;
293  auto slashpos = firstarg.rfind("\\");
294  if (slashpos != std::string::npos)
295  firstarg.erase(0, slashpos + 1);
296  slashpos = firstarg.rfind("/");
297  if (slashpos != std::string::npos)
298  firstarg.erase(0, slashpos + 1);
299  argv.push_back((char *)firstarg.c_str());
300 
301  std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
302  for (Int_t n = 1; n <= fargs->GetLast(); ++n)
303  argv.push_back((char *)fargs->At(n)->GetName());
304  argv.push_back(nullptr);
305 
306  R__DEBUG_HERE("WebDisplay") << "Showing web window in " << fProg << " with:\n" << exec;
307 
308  _spawnv(_P_NOWAIT, fProg.c_str(), argv.data());
309 
310 #else
311 
312 #ifdef R__MACOSX
313  std::string prog = std::regex_replace(fProg, std::regex(" "), "\\ ");
314 #else
315  std::string prog = fProg;
316 #endif
317 
318  exec = std::regex_replace(exec, std::regex("\\$prog"), prog);
319 
320  R__DEBUG_HERE("WebDisplay") << "Showing web window in browser with:\n" << exec;
321 
322  gSystem->Exec(exec.c_str());
323 #endif
324 
325  // add rmdir if required
326  return std::make_unique<RWebBrowserHandle>(url, rmdir);
327 }
328 
329 //////////////////////////////////////////////////////////////////////////////////////////////////
330 /// Constructor
331 
332 ROOT::Experimental::RWebDisplayHandle::ChromeCreator::ChromeCreator() : BrowserCreator(true)
333 {
334  TestProg(gEnv->GetValue("WebGui.Chrome", ""));
335 
336 #ifdef _MSC_VER
337  TestProg("\\Google\\Chrome\\Application\\chrome.exe", true);
338 #endif
339 #ifdef R__MACOSX
340  TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
341 #endif
342 #ifdef R__LINUX
343  TestProg("/usr/bin/chromium");
344  TestProg("/usr/bin/chromium-browser");
345  TestProg("/usr/bin/chrome-browser");
346 #endif
347 
348 #ifdef _MSC_VER
349  fBatchExec = gEnv->GetValue("WebGui.ChromeBatch", "fork: --headless --disable-gpu $url");
350  fExec = gEnv->GetValue("WebGui.ChromeInteractive", "$prog $geometry --no-first-run --app=$url");
351 #else
352  fBatchExec = gEnv->GetValue("WebGui.ChromeBatch", "fork:--headless --incognito $url");
353  fExec = gEnv->GetValue("WebGui.ChromeInteractive", "$prog $geometry --no-first-run --incognito --app=\'$url\' &");
354 #endif
355 }
356 
357 void ROOT::Experimental::RWebDisplayHandle::ChromeCreator::ProcessGeometry(std::string &exec, const RWebDisplayArgs &args)
358 {
359  std::string size, pos;
360  if ((args.GetWidth() > 0) || (args.GetHeight() > 0))
361  size = "--window-size="s + std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800) + ","s +
362  std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600);
363  if ((args.GetX() >= 0) || (args.GetY() >= 0))
364  pos = " --window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
365  std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
366 
367  exec = std::regex_replace(exec, std::regex("\\$geometry"), size + pos);
368 }
369 
370 
371 //////////////////////////////////////////////////////////////////////////////////////////////////
372 /// Handle profile argument
373 
374 std::string ROOT::Experimental::RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
375 {
376  std::string rmdir, profile_arg;
377 
378  if (exec.find("$profile") == std::string::npos)
379  return rmdir;
380 
381  const char *chrome_profile = gEnv->GetValue("WebGui.ChromeProfile", "");
382  if (chrome_profile && *chrome_profile) {
383  profile_arg = chrome_profile;
384  } else {
385  gRandom->SetSeed(0);
386  rmdir = profile_arg = std::string(gSystem->TempDirectory()) + "/root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000));
387  }
388 
389  exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
390 
391  return rmdir;
392 }
393 
394 
395 //////////////////////////////////////////////////////////////////////////////////////////////////
396 /// Constructor
397 
398 ROOT::Experimental::RWebDisplayHandle::FirefoxCreator::FirefoxCreator() : BrowserCreator(true)
399 {
400  TestProg(gEnv->GetValue("WebGui.Firefox", ""));
401 
402 #ifdef _MSC_VER
403  TestProg("\\Mozilla Firefox\\firefox.exe", true);
404 #endif
405 #ifdef R__MACOSX
406  TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
407 #endif
408 #ifdef R__LINUX
409  TestProg("/usr/bin/firefox");
410 #endif
411 
412 #ifdef _MSC_VER
413  // there is a problem when specifying the window size with wmic on windows:
414  // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
415  fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "fork: -headless -no-remote $profile $url");
416  fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $url");
417 #else
418  fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "fork:--headless --private-window --no-remote $profile $url");
419  fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog --private-window \'$url\' &");
420 #endif
421 }
422 
423 //////////////////////////////////////////////////////////////////////////////////////////////////
424 /// Create Firefox profile to run independent browser window
425 
426 std::string ROOT::Experimental::RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bool batch_mode)
427 {
428  std::string rmdir, profile_arg;
429 
430  if (exec.find("$profile") == std::string::npos)
431  return rmdir;
432 
433  const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
434  const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
435  Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 0);
436  if (ff_profile && *ff_profile) {
437  profile_arg = "-P "s + ff_profile;
438  } else if (ff_profilepath && *ff_profilepath) {
439  profile_arg = "-profile "s + ff_profilepath;
440  } else if ((ff_randomprofile > 0) || (batch_mode && (ff_randomprofile >= 0))) {
441 
442  gRandom->SetSeed(0);
443  std::string rnd_profile = "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000));
444  std::string profile_dir = std::string(gSystem->TempDirectory()) + "/"s + rnd_profile;
445 
446  profile_arg = "-profile "s + profile_dir;
447 
448  if (gSystem->mkdir(profile_dir.c_str()) == 0) {
449  rmdir = profile_dir;
450  } else {
451  R__ERROR_HERE("WebDisplay") << "Cannot create Firefox profile directory " << profile_dir;
452  }
453  }
454 
455  exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
456 
457  return rmdir;
458 }
459 
460 
461 ///////////////////////////////////////////////////////////////////////////////////////////////////
462 /// Create web display
463 /// \param args - defines where and how to display web window
464 /// Returns RWebDisplayHandle, which holds information of running browser application
465 /// Can be used fully independent from RWebWindow classes just to show any web page
466 
467 std::unique_ptr<ROOT::Experimental::RWebDisplayHandle> ROOT::Experimental::RWebDisplayHandle::Display(const RWebDisplayArgs &args)
468 {
469  std::unique_ptr<RWebDisplayHandle> handle;
470 
471  auto try_creator = [&](std::unique_ptr<Creator> &creator) {
472  if (!creator || !creator->IsActive())
473  return false;
474  handle = creator->Display(args);
475  return handle ? true : false;
476  };
477 
478  if ((args.GetBrowserKind() == RWebDisplayArgs::kLocal) || (args.GetBrowserKind() == RWebDisplayArgs::kCEF)) {
479  if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
480  return handle;
481  }
482 
483  if ((args.GetBrowserKind() == RWebDisplayArgs::kLocal) || (args.GetBrowserKind() == RWebDisplayArgs::kQt5)) {
484  if (try_creator(FindCreator("qt5", "libROOTQt5WebDisplay")))
485  return handle;
486  }
487 
488  if (args.IsLocalDisplay()) {
489  R__ERROR_HERE("WebDisplay") << "Neither Qt5 nor CEF libraries were found to provide local display";
490  return handle;
491  }
492 
493  if ((args.GetBrowserKind() == RWebDisplayArgs::kNative) || (args.GetBrowserKind() == RWebDisplayArgs::kChrome)) {
494  if (try_creator(FindCreator("chrome", "ChromeCreator")))
495  return handle;
496  }
497 
498  if ((args.GetBrowserKind() == RWebDisplayArgs::kNative) || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox)) {
499  if (try_creator(FindCreator("firefox", "FirefoxCreator")))
500  return handle;
501  }
502 
503  if ((args.GetBrowserKind() == RWebDisplayArgs::kChrome) || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox)) {
504  R__ERROR_HERE("WebDisplay") << "Neither Chrome nor Firefox browser cannot be started to provide display";
505  return handle;
506  }
507 
508  if ((args.GetBrowserKind() == RWebDisplayArgs::kCustom)) {
509  std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
510  try_creator(creator);
511  } else {
512  try_creator(FindCreator("browser", "BrowserCreator"));
513  }
514 
515  return handle;
516 }
517 
518 ///////////////////////////////////////////////////////////////////////////////////////////////////
519 /// Display provided url in configured web browser
520 /// \param url - specified URL address like https://root.cern
521 /// Browser can specified when starting `root --web=firefox`
522 /// Returns true when browser started
523 /// It is convenience method, equivalent to:
524 /// ~~~
525 /// RWebDisplayArgs args;
526 /// args.SetUrl(url);
527 /// args.SetStandalone(false);
528 /// auto handle = RWebDisplayHandle::Display(args);
529 /// ~~~
530 
531 bool ROOT::Experimental::RWebDisplayHandle::DisplayUrl(const std::string &url)
532 {
533  RWebDisplayArgs args;
534  args.SetUrl(url);
535  args.SetStandalone(false);
536 
537  auto handle = Display(args);
538 
539  return !!handle;
540 }