Logo ROOT  
Reference Guide
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
39using namespace std::string_literals;
40
41//////////////////////////////////////////////////////////////////////////////////////////////////
42/// Static holder of registered creators of web displays
43
44std::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
56std::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
82namespace ROOT {
83namespace Experimental {
84
85//////////////////////////////////////////////////////////////////////////////////////////////////
86/// Specialized handle to hold information about running browser process
87/// Used to correctly cleanup all processes and temporary directories
88
89class 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
100public:
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
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
160void 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
194std::unique_ptr<ROOT::Experimental::RWebDisplayHandle>
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
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
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
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
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
426std::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
467std::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
479 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
480 return handle;
481 }
482
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
494 if (try_creator(FindCreator("chrome", "ChromeCreator")))
495 return handle;
496 }
497
499 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
500 return handle;
501 }
502
504 R__ERROR_HERE("WebDisplay") << "Neither Chrome nor Firefox browser cannot be started to provide display";
505 return handle;
506 }
507
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
532{
533 RWebDisplayArgs args;
534 args.SetUrl(url);
535 args.SetStandalone(false);
536
537 auto handle = Display(args);
538
539 return !!handle;
540}
#define R__ERROR_HERE(GROUP)
Definition: RLogger.hxx:183
#define R__DEBUG_HERE(GROUP)
Definition: RLogger.hxx:186
#define c(i)
Definition: RSha256.hxx:101
static RooMathCoreReg dummy
int Int_t
Definition: RtypesCore.h:41
R__EXTERN TEnv * gEnv
Definition: TEnv.h:171
char name[80]
Definition: TGX11.cxx:109
R__EXTERN TRandom * gRandom
Definition: TRandom.h:62
@ kExecutePermission
Definition: TSystem.h:46
R__EXTERN TSystem * gSystem
Definition: TSystem.h:560
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetCustomExec() const
returns custom executable to start web browser
bool IsHeadless() const
returns headless mode
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
int GetHeight() const
returns preferable web window height
std::string GetFullUrl() const
returns window url with append options
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
bool IsStandalone() const
Return true if browser should runs in standalone mode.
bool IsLocalDisplay() const
returns true if local display like CEF or Qt5 QWebEngine should be used
@ kFirefox
Mozilla Firefox browser.
@ kCEF
Chromium Embedded Framework - local display with CEF libs.
@ kCustom
custom web browser, execution string should be provided
@ kNative
either Chrome or Firefox - both support major functionality
@ kLocal
either CEF or Qt5 - both runs on local display without real http server
@ kQt5
QWebEngine libraries - Chrome code packed in qt5.
int GetY() const
set preferable web window y position
int GetX() const
set preferable web window x position
int GetWidth() const
returns preferable web window width
void TestProg(const std::string &nexttry, bool check_std_paths=false)
Check if browser executable exists and can be used.
BrowserCreator(bool custom=true, const std::string &exec="")
Class to handle starting of web-browsers like Chrome or Firefox.
std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args) override
Display given URL in web browser.
void ProcessGeometry(std::string &, const RWebDisplayArgs &args) override
std::string MakeProfile(std::string &exec, bool) override
Handle profile argument.
std::string MakeProfile(std::string &exec, bool batch) override
Create Firefox profile to run independent browser window.
static bool DisplayUrl(const std::string &url)
Display provided url in configured web browser.
static std::unique_ptr< Creator > & FindCreator(const std::string &name, const std::string &libname="")
Search for specific browser creator If not found, try to add one.
static std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
static std::map< std::string, std::unique_ptr< Creator > > & GetMap()
!< URL used to launch display
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition: TEnv.cxx:491
virtual Bool_t InheritsFrom(const char *classname) const
Returns kTRUE if object inherits from class "classname".
Definition: TObject.cxx:443
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition: TRandom.cxx:597
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition: TRandom.cxx:349
Basic string class.
Definition: TString.h:131
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition: TString.cxx:2197
virtual const char * Getenv(const char *env)
Get environment variable.
Definition: TSystem.cxx:1653
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition: TSystem.cxx:914
virtual Int_t Exec(const char *shellcmd)
Execute a command.
Definition: TSystem.cxx:663
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition: TSystem.cxx:1845
virtual Bool_t AccessPathName(const char *path, EAccessMode mode=kFileExists)
Returns FALSE if one can access a file using the specified access mode.
Definition: TSystem.cxx:1287
virtual TString GetFromPipe(const char *command)
Execute command and return output in TString.
Definition: TSystem.cxx:690
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition: TSystem.cxx:1473
const Int_t n
Definition: legend1.C:16
VSD Structures.
Definition: StringConv.hxx:21
static constexpr double s
auto * m
Definition: textangle.C:8