Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RWebDisplayHandle.cxx
Go to the documentation of this file.
1// Author: Sergey Linev <s.linev@gsi.de>
2// Date: 2018-10-17
3// Warning: This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!
4
5/*************************************************************************
6 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
14
15#include <ROOT/RLogger.hxx>
16
17#include "RConfigure.h"
18#include "TSystem.h"
19#include "TRandom.h"
20#include "TString.h"
21#include "TObjArray.h"
22#include "THttpServer.h"
23#include "TEnv.h"
24#include "TError.h"
25#include "TROOT.h"
26#include "TBase64.h"
27#include "TBufferJSON.h"
28
29#include <fstream>
30#include <iostream>
31#include <memory>
32#include <regex>
33
34#ifdef _MSC_VER
35#include <process.h>
36#else
37#include <unistd.h>
38#include <stdlib.h>
39#include <signal.h>
40#include <spawn.h>
41#endif
42
43using namespace ROOT;
44using namespace std::string_literals;
45
46/** \class ROOT::RWebDisplayHandle
47\ingroup webdisplay
48
49Handle of created web-based display
50Depending from type of web display, holds handle of started browser process or other display-specific information
51to correctly stop and cleanup display.
52*/
53
54
55//////////////////////////////////////////////////////////////////////////////////////////////////
56/// Static holder of registered creators of web displays
57
58std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> &RWebDisplayHandle::GetMap()
59{
60 static std::map<std::string, std::unique_ptr<RWebDisplayHandle::Creator>> sMap;
61 return sMap;
62}
63
64//////////////////////////////////////////////////////////////////////////////////////////////////
65/// Search for specific browser creator
66/// If not found, try to add one
67/// \param name - creator name like ChromeCreator
68/// \param libname - shared library name where creator could be provided
69
70std::unique_ptr<RWebDisplayHandle::Creator> &RWebDisplayHandle::FindCreator(const std::string &name, const std::string &libname)
71{
72 auto &m = GetMap();
73 auto search = m.find(name);
74 if (search == m.end()) {
75
76 if (libname == "ChromeCreator") {
77 m.emplace(name, std::make_unique<ChromeCreator>(name == "edge"));
78 } else if (libname == "FirefoxCreator") {
79 m.emplace(name, std::make_unique<FirefoxCreator>());
80 } else if (libname == "BrowserCreator") {
81 m.emplace(name, std::make_unique<BrowserCreator>(false));
82 } else if (!libname.empty()) {
83 gSystem->Load(libname.c_str());
84 }
85
86 search = m.find(name); // try again
87 }
88
89 if (search != m.end())
90 return search->second;
91
92 static std::unique_ptr<RWebDisplayHandle::Creator> dummy;
93 return dummy;
94}
95
96namespace ROOT {
97
98//////////////////////////////////////////////////////////////////////////////////////////////////
99/// Specialized handle to hold information about running browser process
100/// Used to correctly cleanup all processes and temporary directories
101
103
104#ifdef _MSC_VER
105 typedef int browser_process_id;
106#else
107 typedef pid_t browser_process_id;
108#endif
109 std::string fTmpDir; ///< temporary directory to delete at the end
110 bool fHasPid{false};
112
113public:
114 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &dump) : RWebDisplayHandle(url), fTmpDir(tmpdir)
115 {
116 SetContent(dump);
117 }
118
119 RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid)
120 : RWebDisplayHandle(url), fTmpDir(tmpdir), fHasPid(true), fPid(pid)
121 {
122 }
123
125 {
126#ifdef _MSC_VER
127 if (fHasPid)
128 gSystem->Exec(("taskkill /F /PID "s + std::to_string(fPid) + " >NUL 2>NUL").c_str());
129 std::string rmdir = "rmdir /S /Q ";
130#else
131 if (fHasPid)
132 kill(fPid, SIGKILL);
133 std::string rmdir = "rm -rf ";
134#endif
135 if (!fTmpDir.empty())
136 gSystem->Exec((rmdir + fTmpDir).c_str());
137 }
138
139};
140
141} // namespace ROOT
142
143//////////////////////////////////////////////////////////////////////////////////////////////////
144/// Class to handle starting of web-browsers like Chrome or Firefox
145
146RWebDisplayHandle::BrowserCreator::BrowserCreator(bool custom, const std::string &exec)
147{
148 if (custom) return;
149
150 if (!exec.empty()) {
151 if (exec.find("$url") == std::string::npos) {
152 fProg = exec;
153#ifdef _MSC_VER
154 fExec = exec + " $url";
155#else
156 fExec = exec + " $url &";
157#endif
158 } else {
159 fExec = exec;
160 auto pos = exec.find(" ");
161 if (pos != std::string::npos)
162 fProg = exec.substr(0, pos);
163 }
164 } else if (gSystem->InheritsFrom("TMacOSXSystem")) {
165 fExec = "open \'$url\'";
166 } else if (gSystem->InheritsFrom("TWinNTSystem")) {
167 fExec = "start $url";
168 } else {
169 fExec = "xdg-open \'$url\' &";
170 }
171}
172
173//////////////////////////////////////////////////////////////////////////////////////////////////
174/// Check if browser executable exists and can be used
175
176void RWebDisplayHandle::BrowserCreator::TestProg(const std::string &nexttry, bool check_std_paths)
177{
178 if (nexttry.empty() || !fProg.empty())
179 return;
180
181 if (!gSystem->AccessPathName(nexttry.c_str(), kExecutePermission)) {
182#ifdef R__MACOSX
183 fProg = std::regex_replace(nexttry, std::regex("%20"), " ");
184#else
185 fProg = nexttry;
186#endif
187 return;
188 }
189
190 if (!check_std_paths)
191 return;
192
193#ifdef _MSC_VER
194 std::string ProgramFiles = gSystem->Getenv("ProgramFiles");
195 auto pos = ProgramFiles.find(" (x86)");
196 if (pos != std::string::npos)
197 ProgramFiles.erase(pos, 6);
198 std::string ProgramFilesx86 = gSystem->Getenv("ProgramFiles(x86)");
199
200 if (!ProgramFiles.empty())
201 TestProg(ProgramFiles + nexttry, false);
202 if (!ProgramFilesx86.empty())
203 TestProg(ProgramFilesx86 + nexttry, false);
204#endif
205}
206
207//////////////////////////////////////////////////////////////////////////////////////////////////
208/// Display given URL in web browser
209
210std::unique_ptr<RWebDisplayHandle>
212{
213 std::string url = args.GetFullUrl();
214 if (url.empty())
215 return nullptr;
216
218 std::cout << "New web window: " << url << std::endl;
219 return std::make_unique<RWebBrowserHandle>(url, "", "");
220 }
221
222 std::string exec;
223 if (args.IsBatchMode())
224 exec = fBatchExec;
225 else if (args.IsHeadless())
226 exec = fHeadlessExec;
227 else if (args.IsStandalone())
228 exec = fExec;
229 else
230 exec = "$prog $url &";
231
232 if (exec.empty())
233 return nullptr;
234
235 std::string swidth = std::to_string(args.GetWidth() > 0 ? args.GetWidth() : 800),
236 sheight = std::to_string(args.GetHeight() > 0 ? args.GetHeight() : 600),
237 sposx = std::to_string(args.GetX() >= 0 ? args.GetX() : 0),
238 sposy = std::to_string(args.GetY() >= 0 ? args.GetY() : 0);
239
240 ProcessGeometry(exec, args);
241
242 std::string rmdir = MakeProfile(exec, args.IsHeadless());
243
244 exec = std::regex_replace(exec, std::regex("\\$url"), url);
245 exec = std::regex_replace(exec, std::regex("\\$width"), swidth);
246 exec = std::regex_replace(exec, std::regex("\\$height"), sheight);
247 exec = std::regex_replace(exec, std::regex("\\$posx"), sposx);
248 exec = std::regex_replace(exec, std::regex("\\$posy"), sposy);
249
250 if (exec.compare(0,5,"fork:") == 0) {
251 if (fProg.empty()) {
252 R__LOG_ERROR(WebGUILog()) << "Fork instruction without executable";
253 return nullptr;
254 }
255
256 exec.erase(0, 5);
257
258#ifndef _MSC_VER
259
260 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
261 if (!fargs || (fargs->GetLast()<=0)) {
262 R__LOG_ERROR(WebGUILog()) << "Fork instruction is empty";
263 return nullptr;
264 }
265
266 std::vector<char *> argv;
267 argv.push_back((char *) fProg.c_str());
268 for (Int_t n = 0; n <= fargs->GetLast(); ++n)
269 argv.push_back((char *)fargs->At(n)->GetName());
270 argv.push_back(nullptr);
271
272 R__LOG_DEBUG(0, WebGUILog()) << "Show web window in browser with posix_spawn:\n" << fProg << " " << exec;
273
274 pid_t pid;
275 int status = posix_spawn(&pid, argv[0], nullptr, nullptr, argv.data(), nullptr);
276 if (status != 0) {
277 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << argv[0];
278 return nullptr;
279 }
280
281 // add processid and rm dir
282
283 return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
284
285#else
286
287 if (fProg.empty()) {
288 R__LOG_ERROR(WebGUILog()) << "No Web browser found";
289 return nullptr;
290 }
291
292 // use UnixPathName to simplify handling of backslashes
293 exec = "wmic process call create '"s + gSystem->UnixPathName(fProg.c_str()) + exec + "' | find \"ProcessId\" "s;
294 std::string process_id = gSystem->GetFromPipe(exec.c_str()).Data();
295 std::stringstream ss(process_id);
296 std::string tmp;
297 char c;
298 int pid = 0;
299 ss >> tmp >> c >> pid;
300
301 if (pid <= 0) {
302 R__LOG_ERROR(WebGUILog()) << "Fail to launch " << fProg;
303 return nullptr;
304 }
305
306 // add processid and rm dir
307 return std::make_unique<RWebBrowserHandle>(url, rmdir, pid);
308#endif
309 }
310
311#ifdef _MSC_VER
312
313 if (exec.rfind("&") == exec.length() - 1) {
314
315 // if last symbol is &, use _spawn to detach execution
316 exec.resize(exec.length() - 1);
317
318 std::vector<char *> argv;
319 std::string firstarg = fProg;
320 auto slashpos = firstarg.find_last_of("/\\");
321 if (slashpos != std::string::npos)
322 firstarg.erase(0, slashpos + 1);
323 argv.push_back((char *)firstarg.c_str());
324
325 std::unique_ptr<TObjArray> fargs(TString(exec.c_str()).Tokenize(" "));
326 for (Int_t n = 1; n <= fargs->GetLast(); ++n)
327 argv.push_back((char *)fargs->At(n)->GetName());
328 argv.push_back(nullptr);
329
330 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in " << fProg << " with:\n" << exec;
331
332 _spawnv(_P_NOWAIT, gSystem->UnixPathName(fProg.c_str()), argv.data());
333
334 return std::make_unique<RWebBrowserHandle>(url, rmdir, ""s);
335 }
336
337 std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s;
338
339#else
340
341#ifdef R__MACOSX
342 std::string prog = std::regex_replace(fProg, std::regex(" "), "\\ ");
343#else
344 std::string prog = fProg;
345#endif
346
347#endif
348
349 exec = std::regex_replace(exec, std::regex("\\$prog"), prog);
350
351 std::string redirect = args.GetRedirectOutput(), dump_content;
352
353 if (!redirect.empty()) {
354 auto p = exec.length();
355 if (exec.rfind("&") == p-1) --p;
356 exec.insert(p, " >"s + redirect + " "s);
357 }
358
359 R__LOG_DEBUG(0, WebGUILog()) << "Showing web window in browser with:\n" << exec;
360
361 gSystem->Exec(exec.c_str());
362
363 // read content of redirected output
364 if (!redirect.empty()) {
365 dump_content = THttpServer::ReadFileContent(redirect.c_str());
366
367 gSystem->Unlink(redirect.c_str());
368 }
369
370 // add rmdir if required
371 return std::make_unique<RWebBrowserHandle>(url, rmdir, dump_content);
372}
373
374//////////////////////////////////////////////////////////////////////////////////////////////////
375/// Constructor
376
378{
379 fEdge = _edge;
380
381 fEnvPrefix = fEdge ? "WebGui.Edge" : "WebGui.Chrome";
382
383 TestProg(gEnv->GetValue(fEnvPrefix.c_str(), ""));
384
385 if (!fProg.empty() && !fEdge)
386 fChromeVersion = gEnv->GetValue("WebGui.ChromeVersion", -1);
387
388#ifdef _MSC_VER
389 if (fEdge)
390 TestProg("\\Microsoft\\Edge\\Application\\msedge.exe", true);
391 else
392 TestProg("\\Google\\Chrome\\Application\\chrome.exe", true);
393#endif
394#ifdef R__MACOSX
395 TestProg("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
396#endif
397#ifdef R__LINUX
398 TestProg("/usr/bin/chromium");
399 TestProg("/usr/bin/chromium-browser");
400 TestProg("/usr/bin/chrome-browser");
401 TestProg("/usr/bin/google-chrome-stable");
402 TestProg("/usr/bin/google-chrome");
403#endif
404
405#ifdef _MSC_VER
406 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless $geometry $url");
407 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --disable-gpu $geometry \"$url\" --dump-dom &");
408 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn
409#else
410#ifdef R__MACOSX
411 bool use_normal = true; // mac does not like new flag
412#else
413 bool use_normal = fChromeVersion < 119;
414#endif
415 if (use_normal) {
416 // old browser with standard headless mode
417 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url");
418 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry \'$url\' --dump-dom >/dev/null &");
419 } else {
420 // newer version with headless=new mode
421 fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless=new --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url");
422 fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless=new --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry \'$url\' &");
423 }
424 fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' &");
425#endif
426}
427
428
429//////////////////////////////////////////////////////////////////////////////////////////////////
430/// Replace $geometry placeholder with geometry settings
431/// Also RWebDisplayArgs::GetExtraArgs() are appended
432
434{
435 std::string geometry;
436 if ((args.GetWidth() > 0) && (args.GetHeight() > 0))
437 geometry = "--window-size="s + std::to_string(args.GetWidth())
438 + (args.IsHeadless() ? "x"s : ","s)
439 + std::to_string(args.GetHeight());
440
441 if (((args.GetX() >= 0) || (args.GetY() >= 0)) && !args.IsHeadless()) {
442 if (!geometry.empty()) geometry.append(" ");
443 geometry.append("--window-position="s + std::to_string(args.GetX() >= 0 ? args.GetX() : 0) + ","s +
444 std::to_string(args.GetY() >= 0 ? args.GetY() : 0));
445 }
446
447 if (!args.GetExtraArgs().empty()) {
448 if (!geometry.empty()) geometry.append(" ");
449 geometry.append(args.GetExtraArgs());
450 }
451
452 exec = std::regex_replace(exec, std::regex("\\$geometry"), geometry);
453}
454
455
456//////////////////////////////////////////////////////////////////////////////////////////////////
457/// Handle profile argument
458
459std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, bool)
460{
461 std::string rmdir, profile_arg;
462
463 if (exec.find("$profile") == std::string::npos)
464 return rmdir;
465
466 const char *chrome_profile = gEnv->GetValue((fEnvPrefix + "Profile").c_str(), "");
467 if (chrome_profile && *chrome_profile) {
468 profile_arg = chrome_profile;
469 } else {
470 gRandom->SetSeed(0);
471 std::string rnd_profile = "root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000));
472 profile_arg = gSystem->TempDirectory();
473
474#ifdef _MSC_VER
475 profile_arg += "\\"s + rnd_profile;
476#else
477 profile_arg += "/"s + rnd_profile;
478#endif
479 rmdir = profile_arg;
480 }
481
482 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
483
484 return rmdir;
485}
486
487
488//////////////////////////////////////////////////////////////////////////////////////////////////
489/// Constructor
490
492{
493 TestProg(gEnv->GetValue("WebGui.Firefox", ""));
494
495#ifdef _MSC_VER
496 TestProg("\\Mozilla Firefox\\firefox.exe", true);
497#endif
498#ifdef R__MACOSX
499 TestProg("/Applications/Firefox.app/Contents/MacOS/firefox");
500#endif
501#ifdef R__LINUX
502 TestProg("/usr/bin/firefox");
503 TestProg("/usr/bin/firefox-bin");
504#endif
505
506#ifdef _MSC_VER
507 // there is a problem when specifying the window size with wmic on windows:
508 // It gives: Invalid format. Hint: <paramlist> = <param> [, <paramlist>].
509 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog -headless -no-remote $profile $url");
510 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "$prog -headless -no-remote $profile $url &");
511 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog -no-remote $profile $url &");
512#else
513 fBatchExec = gEnv->GetValue("WebGui.FirefoxBatch", "$prog --headless --private-window --no-remote $profile $url");
514 fHeadlessExec = gEnv->GetValue("WebGui.FirefoxHeadless", "fork:--headless --private-window --no-remote $profile $url");
515 fExec = gEnv->GetValue("WebGui.FirefoxInteractive", "$prog --private-window \'$url\' &");
516#endif
517}
518
519//////////////////////////////////////////////////////////////////////////////////////////////////
520/// Create Firefox profile to run independent browser window
521
522std::string RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bool batch_mode)
523{
524 std::string rmdir, profile_arg;
525
526 if (exec.find("$profile") == std::string::npos)
527 return rmdir;
528
529 const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", "");
530 const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", "");
531 Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 0);
532 if (ff_profile && *ff_profile) {
533 profile_arg = "-P "s + ff_profile;
534 } else if (ff_profilepath && *ff_profilepath) {
535 profile_arg = "-profile "s + ff_profilepath;
536 } else if ((ff_randomprofile > 0) || (batch_mode && (ff_randomprofile >= 0))) {
537
538 gRandom->SetSeed(0);
539 std::string rnd_profile = "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000));
540 std::string profile_dir = gSystem->TempDirectory();
541
542#ifdef _MSC_VER
543 profile_dir += "\\"s + rnd_profile;
544#else
545 profile_dir += "/"s + rnd_profile;
546#endif
547
548 profile_arg = "-profile "s + profile_dir;
549
550 if (gSystem->mkdir(profile_dir.c_str()) == 0) {
551 rmdir = profile_dir;
552
553 if (batch_mode) {
554 std::ofstream user_js(profile_dir + "/user.js", std::ios::trunc);
555 user_js << "user_pref(\"browser.dom.window.dump.enabled\", true);" << std::endl;
556 // workaround for current Firefox, without such settings it fail to close window and terminate it from batch
557 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyAcceptedVersion\", 2);" << std::endl;
558 user_js << "user_pref(\"datareporting.policy.dataSubmissionPolicyNotifiedTime\", \"1635760572813\");" << std::endl;
559 }
560
561 } else {
562 R__LOG_ERROR(WebGUILog()) << "Cannot create Firefox profile directory " << profile_dir;
563 }
564 }
565
566 exec = std::regex_replace(exec, std::regex("\\$profile"), profile_arg);
567
568 return rmdir;
569}
570
571///////////////////////////////////////////////////////////////////////////////////////////////////
572/// Create web display
573/// \param args - defines where and how to display web window
574/// Returns RWebDisplayHandle, which holds information of running browser application
575/// Can be used fully independent from RWebWindow classes just to show any web page
576
577std::unique_ptr<RWebDisplayHandle> RWebDisplayHandle::Display(const RWebDisplayArgs &args)
578{
579 std::unique_ptr<RWebDisplayHandle> handle;
580
582 return handle;
583
584 auto try_creator = [&](std::unique_ptr<Creator> &creator) {
585 if (!creator || !creator->IsActive())
586 return false;
587 handle = creator->Display(args);
588 return handle ? true : false;
589 };
590
592 if (try_creator(FindCreator("cef", "libROOTCefDisplay")))
593 return handle;
594 }
595
597 if (try_creator(FindCreator("qt5", "libROOTQt5WebDisplay")))
598 return handle;
599 }
600
602 if (try_creator(FindCreator("qt6", "libROOTQt6WebDisplay")))
603 return handle;
604 }
605
606 if (args.IsLocalDisplay()) {
607 R__LOG_ERROR(WebGUILog()) << "Neither Qt5/6 nor CEF libraries were found to provide local display";
608 return handle;
609 }
610
611 bool handleAsNative = (args.GetBrowserKind() == RWebDisplayArgs::kNative) ||
613
614#ifdef _MSC_VER
615 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
616 if (try_creator(FindCreator("edge", "ChromeCreator")))
617 return handle;
618 }
619#endif
620
621 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kChrome)) {
622 if (try_creator(FindCreator("chrome", "ChromeCreator")))
623 return handle;
624 }
625
626 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox)) {
627 if (try_creator(FindCreator("firefox", "FirefoxCreator")))
628 return handle;
629 }
630
631 if (handleAsNative || (args.GetBrowserKind() == RWebDisplayArgs::kChrome) || (args.GetBrowserKind() == RWebDisplayArgs::kFirefox) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge)) {
632 // R__LOG_ERROR(WebGUILog()) << "Neither Chrome nor Firefox browser cannot be started to provide display";
633 return handle;
634 }
635
637 std::unique_ptr<Creator> creator = std::make_unique<BrowserCreator>(false, args.GetCustomExec());
638 try_creator(creator);
639 } else {
640 try_creator(FindCreator("browser", "BrowserCreator"));
641 }
642
643 return handle;
644}
645
646///////////////////////////////////////////////////////////////////////////////////////////////////
647/// Display provided url in configured web browser
648/// \param url - specified URL address like https://root.cern
649/// Browser can specified when starting `root --web=firefox`
650/// Returns true when browser started
651/// It is convenience method, equivalent to:
652/// ~~~
653/// RWebDisplayArgs args;
654/// args.SetUrl(url);
655/// args.SetStandalone(false);
656/// auto handle = RWebDisplayHandle::Display(args);
657/// ~~~
658
659bool RWebDisplayHandle::DisplayUrl(const std::string &url)
660{
661 RWebDisplayArgs args;
662 args.SetUrl(url);
663 args.SetStandalone(false);
664
665 auto handle = Display(args);
666
667 return !!handle;
668}
669
670///////////////////////////////////////////////////////////////////////////////////////////////////
671/// Checks if configured browser can be used for image production
672
674{
678 bool detected = false;
679
680 auto &h1 = FindCreator("chrome", "ChromeCreator");
681 if (h1 && h1->IsActive()) {
683 detected = true;
684 }
685
686 if (!detected) {
687 auto &h2 = FindCreator("firefox", "FirefoxCreator");
688 if (h2 && h2->IsActive()) {
690 detected = true;
691 }
692 }
693
694#ifdef _MSC_VER
695 if (!detected) {
696 auto &h3 = FindCreator("edge", "ChromeCreator");
697 if (h3 && h3->IsActive()) {
699 detected = true;
700 }
701 }
702#endif
703 return detected;
704 }
705
707 auto &h1 = FindCreator("chrome", "ChromeCreator");
708 return h1 && h1->IsActive();
709 }
710
712 auto &h2 = FindCreator("firefox", "FirefoxCreator");
713 return h2 && h2->IsActive();
714 }
715
716#ifdef _MSC_VER
718 auto &h3 = FindCreator("edge", "ChromeCreator");
719 return h3 && h3->IsActive();
720 }
721#endif
722
723 return true;
724}
725
726///////////////////////////////////////////////////////////////////////////////////////////////////
727/// Returns true if image production for specified browser kind is supported
728/// If browser not specified - use currently configured browser or try to test existing web browsers
729
730bool RWebDisplayHandle::CanProduceImages(const std::string &browser)
731{
732 RWebDisplayArgs args(browser);
733
734 return CheckIfCanProduceImages(args);
735}
736
737///////////////////////////////////////////////////////////////////////////////////////////////////
738/// Produce image file using JSON data as source
739/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
740bool RWebDisplayHandle::ProduceImage(const std::string &fname, const std::string &json, int width, int height, const char *batch_file)
741{
742 return ProduceImages(fname, {json}, {width}, {height}, batch_file);
743}
744
745///////////////////////////////////////////////////////////////////////////////////////////////////
746/// Produce image file(s) using JSON data as source
747/// Invokes JSROOT drawing functionality in headless browser - Google Chrome or Mozilla Firefox
748bool RWebDisplayHandle::ProduceImages(const std::string &fname, const std::vector<std::string> &jsons, const std::vector<int> &widths, const std::vector<int> &heights, const char *batch_file)
749{
750 if (jsons.empty())
751 return false;
752
753 std::string _fname = fname;
754 std::transform(_fname.begin(), _fname.end(), _fname.begin(), ::tolower);
755 auto EndsWith = [&_fname](const std::string &suffix) {
756 return (_fname.length() > suffix.length()) ? (0 == _fname.compare(_fname.length() - suffix.length(), suffix.length(), suffix)) : false;
757 };
758
759 std::vector<std::string> fnames;
760
761 if (!EndsWith(".pdf")) {
762 bool has_quialifier = _fname.find("%") != std::string::npos;
763
764 if (!has_quialifier && (jsons.size() > 1)) {
765 _fname.insert(_fname.rfind("."), "%d");
766 has_quialifier = true;
767 }
768
769 for (unsigned n = 0; n < jsons.size(); n++) {
770 if (has_quialifier) {
771 auto expand_name = TString::Format(_fname.c_str(), (int) n);
772 fnames.emplace_back(expand_name.Data());
773 } else {
774 fnames.emplace_back(_fname);
775 }
776 }
777 }
778
779 if (EndsWith(".json")) {
780 for (unsigned n = 0; n < jsons.size(); ++n) {
781 std::ofstream ofs(fnames[n]);
782 ofs << jsons[n];
783 }
784 return true;
785 }
786
787 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
788 TString jsrootsysdflt;
789 if (!jsrootsys) {
790 jsrootsysdflt = TROOT::GetDataDir() + "/js";
791 if (gSystem->ExpandPathName(jsrootsysdflt)) {
792 R__LOG_ERROR(WebGUILog()) << "Fail to locate JSROOT " << jsrootsysdflt;
793 return false;
794 }
795 jsrootsys = jsrootsysdflt.Data();
796 }
797
798 RWebDisplayArgs args; // set default browser kind, only Chrome/Firefox/Edge or CEF/Qt5/Qt6 can be used here
799 if (!CheckIfCanProduceImages(args)) {
800 R__LOG_ERROR(WebGUILog()) << "Fail to detect supported browsers for image production";
801 return false;
802 }
803
804 auto isChromeBased = (args.GetBrowserKind() == RWebDisplayArgs::kChrome) || (args.GetBrowserKind() == RWebDisplayArgs::kEdge),
805 isFirefox = args.GetBrowserKind() == RWebDisplayArgs::kFirefox;
806
807 std::string draw_kind;
808
809 if (EndsWith(".pdf"))
810 draw_kind = "draw"; // not a JSROOT drawing but Chrome capability to create PDF out of HTML page is used
811 else if (EndsWith("shot.png") && (jsons.size() == 1))
812 draw_kind = isChromeBased ? "draw" : "png"; // using screenshot
813 else if (EndsWith(".svg"))
814 draw_kind = "svg";
815 else if (EndsWith(".png"))
816 draw_kind = "png";
817 else if (EndsWith(".jpg") || EndsWith(".jpeg"))
818 draw_kind = "jpeg";
819 else if (EndsWith(".webp"))
820 draw_kind = "webp";
821 else
822 return false;
823
824 if (!batch_file || !*batch_file)
825 batch_file = "/js/files/canv_batch.htm";
826
827 TString origin = TROOT::GetDataDir() + batch_file;
828 if (gSystem->ExpandPathName(origin)) {
829 R__LOG_ERROR(WebGUILog()) << "Fail to find " << origin;
830 return false;
831 }
832
833 auto filecont = THttpServer::ReadFileContent(origin.Data());
834 if (filecont.empty()) {
835 R__LOG_ERROR(WebGUILog()) << "Fail to read content of " << origin;
836 return false;
837 }
838
839 int max_width = 0, max_height = 0, page_margin = 10;
840 for (auto &w : widths)
841 if (w > max_width)
842 max_width = w;
843 for (auto &h : heights)
844 if (h > max_height)
845 max_height = h;
846
847 auto jsonw = TBufferJSON::ToJSON(&widths, TBufferJSON::kNoSpaces);
848 auto jsonh = TBufferJSON::ToJSON(&heights, TBufferJSON::kNoSpaces);
849
850 std::string mains;
851 for (auto &json : jsons) {
852 mains.append(mains.empty() ? "[" : ", ");
853 mains.append(json);
854 }
855 mains.append("]");
856
857 if (strstr(jsrootsys,"http://") || strstr(jsrootsys,"https://") || strstr(jsrootsys,"file://"))
858 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), jsrootsys);
859 else
860 filecont = std::regex_replace(filecont, std::regex("\\$jsrootsys"), "file://"s + jsrootsys);
861
862 filecont = std::regex_replace(filecont, std::regex("\\$page_margin"), std::to_string(page_margin) + "px");
863 filecont = std::regex_replace(filecont, std::regex("\\$page_width"), std::to_string(max_width + 2*page_margin) + "px");
864 filecont = std::regex_replace(filecont, std::regex("\\$page_height"), std::to_string(max_height + 2*page_margin) + "px");
865
866 filecont = std::regex_replace(filecont, std::regex("\\$draw_kind"), draw_kind);
867 filecont = std::regex_replace(filecont, std::regex("\\$draw_widths"), jsonw.Data());
868 filecont = std::regex_replace(filecont, std::regex("\\$draw_heights"), jsonh.Data());
869 filecont = std::regex_replace(filecont, std::regex("\\$draw_objects"), mains);
870
871 TString dump_name;
872 if (draw_kind == "draw") {
873 if (!isChromeBased) {
874 R__LOG_ERROR(WebGUILog()) << "Creation of PDF files supported only by Chrome-based browser";
875 return false;
876 }
877 } else if (isChromeBased || isFirefox) {
878 dump_name = "canvasdump";
879 FILE *df = gSystem->TempFileName(dump_name);
880 if (!df) {
881 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for dump-dom";
882 return false;
883 }
884 fputs("placeholder", df);
885 fclose(df);
886 }
887
888 // When true, place HTML file into home directory
889 // Some Chrome installation do not allow run html code from files, created in /tmp directory
890 static bool chrome_tmp_workaround = false;
891
892 TString tmp_name, html_name;
893
894try_again:
895
897 args.SetUrl(""s);
898 args.SetPageContent(filecont);
899
900 tmp_name.Clear();
901 html_name.Clear();
902
903 R__LOG_DEBUG(0, WebGUILog()) << "Using file content_len " << filecont.length() << " to produce batch images " << fname;
904
905 } else {
906 tmp_name = "canvasbody";
907 FILE *hf = gSystem->TempFileName(tmp_name);
908 if (!hf) {
909 R__LOG_ERROR(WebGUILog()) << "Fail to create temporary file for batch job";
910 return false;
911 }
912 fputs(filecont.c_str(), hf);
913 fclose(hf);
914
915 html_name = tmp_name + ".html";
916
917 if (chrome_tmp_workaround) {
918 std::string homedir = gSystem->GetHomeDirectory();
919 auto pos = html_name.Last('/');
920 if (pos == kNPOS)
921 html_name = TString::Format("/random%d.html", gRandom->Integer(1000000));
922 else
923 html_name.Remove(0, pos);
924 html_name = homedir + html_name.Data();
925 gSystem->Unlink(html_name.Data());
926 gSystem->Unlink(tmp_name.Data());
927
928 std::ofstream ofs(html_name.Data(), std::ofstream::out);
929 ofs << filecont;
930 } else {
931 if (gSystem->Rename(tmp_name.Data(), html_name.Data()) != 0) {
932 R__LOG_ERROR(WebGUILog()) << "Fail to rename temp file " << tmp_name << " into " << html_name;
933 gSystem->Unlink(tmp_name.Data());
934 return false;
935 }
936 }
937
938 args.SetUrl("file://"s + gSystem->UnixPathName(html_name.Data()));
939 args.SetPageContent(""s);
940
941 R__LOG_DEBUG(0, WebGUILog()) << "Using " << html_name << " content_len " << filecont.length() << " to produce batch images " << fname;
942 }
943
944 TString wait_file_name;
945
946 args.SetStandalone(true);
947 args.SetHeadless(true);
948 args.SetBatchMode(true);
949 args.SetSize(widths[0], heights[0]);
950
951 if (draw_kind == "draw") {
952
953 TString tgtfilename = fname.c_str();
954 if (!gSystem->IsAbsoluteFileName(tgtfilename.Data()))
956
957 wait_file_name = tgtfilename;
958
959 if (EndsWith(".pdf"))
960 args.SetExtraArgs("--print-to-pdf-no-header --print-to-pdf="s + gSystem->UnixPathName(tgtfilename.Data()));
961 else
962 args.SetExtraArgs("--screenshot="s + gSystem->UnixPathName(tgtfilename.Data()));
963
964 // remove target image file - we use it as detection when chrome is ready
965 gSystem->Unlink(tgtfilename.Data());
966
967 } else if (isFirefox) {
968 // firefox will use window.dump to output produced result
969 args.SetRedirectOutput(dump_name.Data());
970 gSystem->Unlink(dump_name.Data());
971 } else if (isChromeBased) {
972 // require temporary output file
973 args.SetExtraArgs("--dump-dom");
974 args.SetRedirectOutput(dump_name.Data());
975
976 // wait_file_name = dump_name;
977
978 gSystem->Unlink(dump_name.Data());
979 }
980
981 auto handle = RWebDisplayHandle::Display(args);
982
983 if (!handle) {
984 R__LOG_DEBUG(0, WebGUILog()) << "Cannot start " << args.GetBrowserName() << " to produce image " << fname;
985 return false;
986 }
987
988 // delete temporary HTML file
989 if (html_name.Length() > 0)
990 gSystem->Unlink(html_name.Data());
991
992 if (!wait_file_name.IsNull() && gSystem->AccessPathName(wait_file_name.Data())) {
993 R__LOG_ERROR(WebGUILog()) << "Fail to produce image " << fname;
994 return false;
995 }
996
997 if (draw_kind != "draw") {
998
999 auto dumpcont = handle->GetContent();
1000
1001 if ((dumpcont.length() > 20) && (dumpcont.length() < 60) && !chrome_tmp_workaround && isChromeBased) {
1002 // chrome creates dummy html file with mostly no content
1003 // problem running chrome from /tmp directory, lets try work from home directory
1004 chrome_tmp_workaround = true;
1005 goto try_again;
1006 }
1007
1008 if (dumpcont.length() < 100) {
1009 R__LOG_ERROR(WebGUILog()) << "Fail to dump HTML code into " << (dump_name.IsNull() ? "CEF" : dump_name.Data());
1010 return false;
1011 }
1012
1013 if (draw_kind == "svg") {
1014
1015 std::string::size_type p = 0;
1016
1017 for (auto & fn : fnames) {
1018 auto p1 = dumpcont.find("<svg", p);
1019 auto p2 = dumpcont.find("</svg></div>", p1 + 4);
1020 p = p2 + 6;
1021 std::ofstream ofs(fn);
1022 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1023 ofs << dumpcont.substr(p1, p2-p1+6);
1024 ::Info("ProduceImage", "SVG file %s size %d bytes has been created", fn.c_str(), (int) (p2-p1+6));
1025 } else {
1026 R__LOG_ERROR(WebGUILog()) << "Fail to extract SVG from HTML dump " << dump_name;
1027 ofs << "Failure!!!\n" << dumpcont;
1028 return false;
1029 }
1030 }
1031 } else {
1032
1033 std::string::size_type p = 0;
1034
1035 for (auto &fn : fnames) {
1036
1037 auto p1 = dumpcont.find(";base64,", p);
1038 auto p2 = dumpcont.find("></div>", p1 + 4);
1039 p = p2 + 5;
1040
1041 if ((p1 != std::string::npos) && (p2 != std::string::npos) && (p1 < p2)) {
1042
1043 auto base64 = dumpcont.substr(p1+8, p2-p1-9);
1044 auto binary = TBase64::Decode(base64.c_str());
1045
1046 std::ofstream ofs(fn, std::ios::binary);
1047 ofs.write(binary.Data(), binary.Length());
1048
1049 ::Info("ProduceImage", "Image file %s size %d bytes has been created", fn.c_str(), (int) binary.Length());
1050 } else {
1051 R__LOG_ERROR(WebGUILog()) << "Fail to extract image from dump HTML code " << dump_name;
1052
1053 return false;
1054 }
1055 }
1056 }
1057 } else if (EndsWith(".pdf")) {
1058 ::Info("ProduceImage", "PDF file %s with %d pages has been created", fname.c_str(), (int) jsons.size());
1059 }
1060
1061 if (fnames.size() == 1)
1062 R__LOG_DEBUG(0, WebGUILog()) << "Create file " << fnames[0];
1063 else
1064 R__LOG_DEBUG(0, WebGUILog()) << "Create files " << TBufferJSON::ToJSON(&fnames, TBufferJSON::kNoSpaces);
1065
1066 return true;
1067}
1068
nlohmann::json json
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:365
#define c(i)
Definition RSha256.hxx:101
#define h(i)
Definition RSha256.hxx:106
constexpr Ssiz_t kNPOS
Definition RtypesCore.h:124
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
void Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
Definition TError.cxx:218
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t width
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t height
char name[80]
Definition TGX11.cxx:110
R__EXTERN TRandom * gRandom
Definition TRandom.h:62
@ kExecutePermission
Definition TSystem.h:45
R__EXTERN TSystem * gSystem
Definition TSystem.h:560
Specialized handle to hold information about running browser process Used to correctly cleanup all pr...
RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid)
std::string fTmpDir
temporary directory to delete at the end
RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &dump)
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetBrowserName() const
Returns configured browser name.
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
const std::string & GetRedirectOutput() const
get file name to which web browser output should be redirected
void SetStandalone(bool on=true)
Set standalone mode for running browser, default on When disabled, normal browser window (or just tab...
void SetBatchMode(bool on=true)
set batch mode
RWebDisplayArgs & SetSize(int w, int h)
set preferable web window width and height
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
int GetWidth() const
returns preferable web window width
RWebDisplayArgs & SetPageContent(const std::string &cont)
set window url
int GetY() const
set preferable web window y position
std::string GetFullUrl() const
returns window url with append options
bool IsStandalone() const
Return true if browser should runs in standalone mode.
int GetHeight() const
returns preferable web window height
RWebDisplayArgs & SetBrowserKind(const std::string &kind)
Set browser kind as string argument.
std::string GetCustomExec() const
returns custom executable to start web browser
void SetExtraArgs(const std::string &args)
set extra command line arguments for starting web browser command
bool IsBatchMode() const
returns batch mode
bool IsHeadless() const
returns headless mode
@ kDefault
default system web browser, can not be used in batch mode
@ kFirefox
Mozilla Firefox browser.
@ kNative
either Chrome or Firefox - both support major functionality
@ kLocal
either CEF or Qt5 - both runs on local display without real http server
@ kServer
indicates that ROOT runs as server and just printouts window URL, browser should be started by the us...
@ kOff
disable web display, do not start any browser
@ kCEF
Chromium Embedded Framework - local display with CEF libs.
@ kQt5
Qt5 QWebEngine libraries - Chromium code packed in qt5.
@ kQt6
Qt6 QWebEngine libraries - Chromium code packed in qt6.
@ kCustom
custom web browser, execution string should be provided
@ kChrome
Google Chrome browser.
@ kEdge
Microsoft Edge browser (Windows only)
void SetRedirectOutput(const std::string &fname="")
specify file name to which web browser output should be redirected
void SetHeadless(bool on=true)
set headless mode
const std::string & GetExtraArgs() const
get extra command line arguments for starting web browser command
int GetX() const
set preferable web window x position
bool IsLocalDisplay() const
returns true if local display like CEF or Qt5 QWebEngine should be used
std::string fBatchExec
batch execute line
std::string fHeadlessExec
headless execute line
std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args) override
Display given URL in web browser.
std::string fExec
standard execute line
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.
ChromeCreator(bool is_edge=false)
Constructor.
void ProcessGeometry(std::string &, const RWebDisplayArgs &args) override
Replace $geometry placeholder with geometry settings Also RWebDisplayArgs::GetExtraArgs() are appende...
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.
Handle of created web-based display Depending from type of web display, holds handle of started brows...
static std::map< std::string, std::unique_ptr< Creator > > & GetMap()
Static holder of registered creators of web displays.
static bool CheckIfCanProduceImages(RWebDisplayArgs &args)
Checks if configured browser can be used for image production.
static bool ProduceImages(const std::string &fname, const std::vector< std::string > &jsons, const std::vector< int > &widths, const std::vector< int > &heights, const char *batch_file=nullptr)
Produce image file(s) using JSON data as source Invokes JSROOT drawing functionality in headless brow...
void SetContent(const std::string &cont)
set content
static bool ProduceImage(const std::string &fname, const std::string &json, int width=800, int height=600, const char *batch_file=nullptr)
Produce image file using JSON data as source Invokes JSROOT drawing functionality in headless browser...
static bool CanProduceImages(const std::string &browser="")
Returns true if image production for specified browser kind is supported If browser not specified - u...
static bool DisplayUrl(const std::string &url)
Display provided url in configured web browser.
static std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
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 TString Decode(const char *data)
Decode a base64 string date into a generic TString.
Definition TBase64.cxx:131
static TString ToJSON(const T *obj, Int_t compact=0, const char *member_name=nullptr)
Definition TBufferJSON.h:75
@ kNoSpaces
no new lines plus remove all spaces around "," and ":" symbols
Definition TBufferJSON.h:39
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition TEnv.cxx:491
static char * ReadFileContent(const char *filename, Int_t &len)
Reads content of file from the disk.
virtual Bool_t InheritsFrom(const char *classname) const
Returns kTRUE if object inherits from class "classname".
Definition TObject.cxx:525
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3041
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition TRandom.cxx:608
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition TRandom.cxx:360
Basic string class.
Definition TString.h:139
Ssiz_t Length() const
Definition TString.h:421
void Clear()
Clear string without changing its capacity.
Definition TString.cxx:1221
const char * Data() const
Definition TString.h:380
Ssiz_t Last(char c) const
Find last occurrence of a character c.
Definition TString.cxx:924
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2242
Bool_t IsNull() const
Definition TString.h:418
TString & Remove(Ssiz_t pos)
Definition TString.h:685
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2356
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1261
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1650
virtual int mkdir(const char *name, Bool_t recursive=kFALSE)
Make a file system directory.
Definition TSystem.cxx:893
virtual Int_t Exec(const char *shellcmd)
Execute a command.
Definition TSystem.cxx:640
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition TSystem.cxx:1842
virtual const char * PrependPathName(const char *dir, TString &name)
Concatenate a directory and a file name.
Definition TSystem.cxx:1068
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:1283
virtual FILE * TempFileName(TString &base, const char *dir=nullptr)
Create a secure temporary file by appending a unique 6 letter string to base.
Definition TSystem.cxx:1484
virtual std::string GetHomeDirectory(const char *userName=nullptr) const
Return the user's home directory.
Definition TSystem.cxx:882
virtual const char * UnixPathName(const char *unixpathname)
Convert from a local pathname to a Unix pathname.
Definition TSystem.cxx:1050
virtual int Rename(const char *from, const char *to)
Rename a file.
Definition TSystem.cxx:1337
virtual TString GetFromPipe(const char *command)
Execute command and return output in TString.
Definition TSystem.cxx:667
virtual Bool_t IsAbsoluteFileName(const char *dir)
Return true if dir is an absolute pathname.
Definition TSystem.cxx:938
virtual const char * WorkingDirectory()
Return working directory.
Definition TSystem.cxx:858
virtual int Unlink(const char *name)
Unlink, i.e.
Definition TSystem.cxx:1368
virtual const char * TempDirectory() const
Return a user configured or systemwide directory to create temporary files in.
Definition TSystem.cxx:1469
const Int_t n
Definition legend1.C:16
TH1F * h1
Definition legend1.C:5
This file contains a specialised ROOT message handler to test for diagnostic in unit tests.
ROOT::Experimental::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
TMarker m
Definition textangle.C:8