1 /*
2 * DesktopMainWindow.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include "DesktopMainWindow.hpp"
17
18 #include <QToolBar>
19 #include <QWebChannel>
20 #include <QWebEngineSettings>
21
22 #include <boost/format.hpp>
23 #include <boost/bind/bind.hpp>
24
25 #include <core/FileSerializer.hpp>
26 #include <core/Macros.hpp>
27 #include <core/text/TemplateFilter.hpp>
28
29 #include "DesktopOptions.hpp"
30 #include "DesktopSlotBinders.hpp"
31 #include "DesktopSessionLauncher.hpp"
32 #include "DesktopJobLauncherOverlay.hpp"
33 #include "RemoteDesktopSessionLauncherOverlay.hpp"
34 #include "DockTileView.hpp"
35 #include "DesktopActivationOverlay.hpp"
36 #include "DesktopSessionServersOverlay.hpp"
37 #include "DesktopRCommandEvaluator.hpp"
38
39 using namespace rstudio::core;
40 using namespace boost::placeholders;
41
42 namespace rstudio {
43 namespace desktop {
44
45 namespace {
46
47 #ifdef _WIN32
48
onDialogStart(HWINEVENTHOOK hook,DWORD event,HWND hwnd,LONG idObject,LONG idChild,DWORD dwEventThread,DWORD dwmsEventTime)49 void CALLBACK onDialogStart(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
50 LONG idObject, LONG idChild,
51 DWORD dwEventThread, DWORD dwmsEventTime)
52 {
53 ::BringWindowToTop(hwnd);
54 }
55
56 #endif
57
58 // number of times we've tried to reload in startup
59 int s_reloadCount = 0;
60
61 // maximum number of times to try reloading
62 const int s_maxReloadCount = 10;
63
64 // amount of time to wait before each reload, in milliseconds
65 const int s_reloadWaitDuration = 200;
66
67 } // end anonymous namespace
68
MainWindow(QUrl url,bool isRemoteDesktop)69 MainWindow::MainWindow(QUrl url,
70 bool isRemoteDesktop) :
71 GwtWindow(false, false, QString(), url, nullptr, nullptr, isRemoteDesktop),
72 isRemoteDesktop_(isRemoteDesktop),
73 menuCallback_(this),
74 gwtCallback_(this, this, isRemoteDesktop),
75 pSessionLauncher_(nullptr),
76 pRemoteSessionLauncher_(nullptr),
77 pLauncher_(new JobLauncher(this)),
78 pCurrentSessionProcess_(nullptr),
79 isErrorDisplayed_(false)
80 {
81 RCommandEvaluator::setMainWindow(this);
82 pToolbar_->setVisible(false);
83
84 #ifdef _WIN32
85 eventHook_ = nullptr;
86 #endif
87
88 // bind GWT callbacks
89 auto* channel = webPage()->webChannel();
90 channel->registerObject(QStringLiteral("desktop"), &gwtCallback_);
91 if (isRemoteDesktop_)
92 {
93 // since the object registration is asynchronous, during the GWT setup code
94 // there is a race condition where the initialization can happen before the
95 // remoteDesktop object is registered, making the GWT application think that
96 // it should use regular desktop objects - to circumvent this, we use a custom
97 // user agent string that the GWT code can detect with 100% success rate to
98 // get around this race condition
99 QString userAgent = webPage()->profile()->httpUserAgent().append(
100 QStringLiteral("; RStudio Remote Desktop"));
101 webPage()->profile()->setHttpUserAgent(userAgent);
102 channel->registerObject(QStringLiteral("remoteDesktop"), &gwtCallback_);
103 }
104 channel->registerObject(QStringLiteral("desktopMenuCallback"), &menuCallback_);
105
106 // disable error page on main window -- we don't want to show the default 404 page
107 // on failure as we show our own error / loading page instead
108 webPage()->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
109
110 // Dummy menu bar to deal with the fact that
111 // the real menu bar isn't ready until well
112 // after startup.
113 #ifndef Q_OS_MAC
114 auto* pMainMenuStub = new QMenuBar(this);
115 pMainMenuStub->addMenu(QString::fromUtf8("File"));
116 pMainMenuStub->addMenu(QString::fromUtf8("Edit"));
117 pMainMenuStub->addMenu(QString::fromUtf8("Code"));
118 pMainMenuStub->addMenu(QString::fromUtf8("View"));
119 pMainMenuStub->addMenu(QString::fromUtf8("Plots"));
120 pMainMenuStub->addMenu(QString::fromUtf8("Session"));
121 pMainMenuStub->addMenu(QString::fromUtf8("Build"));
122 pMainMenuStub->addMenu(QString::fromUtf8("Debug"));
123 pMainMenuStub->addMenu(QString::fromUtf8("Profile"));
124 pMainMenuStub->addMenu(QString::fromUtf8("Tools"));
125 pMainMenuStub->addMenu(QString::fromUtf8("Help"));
126 setMenuBar(pMainMenuStub);
127 #endif
128
129 connect(&menuCallback_, SIGNAL(menuBarCompleted(QMenuBar*)),
130 this, SLOT(setMenuBar(QMenuBar*)));
131 connect(&menuCallback_, SIGNAL(commandInvoked(QString)),
132 this, SLOT(invokeCommand(QString)));
133
134 connect(&menuCallback_, SIGNAL(zoomActualSize()), this, SLOT(zoomActualSize()));
135 connect(&menuCallback_, SIGNAL(zoomIn()), this, SLOT(zoomIn()));
136 connect(&menuCallback_, SIGNAL(zoomOut()), this, SLOT(zoomOut()));
137
138 connect(&gwtCallback_, SIGNAL(workbenchInitialized()),
139 this, SIGNAL(firstWorkbenchInitialized()));
140 connect(&gwtCallback_, SIGNAL(workbenchInitialized()),
141 this, SLOT(onWorkbenchInitialized()));
142 connect(&gwtCallback_, SIGNAL(sessionQuit()),
143 this, SLOT(onSessionQuit()));
144
145 connect(webView(), SIGNAL(onCloseWindowShortcut()),
146 this, SLOT(onCloseWindowShortcut()));
147
148 connect(webView(), &WebView::urlChanged,
149 this, &MainWindow::onUrlChanged);
150
151 connect(webView(), &WebView::loadFinished,
152 this, &MainWindow::onLoadFinished);
153
154 connect(webPage(), &QWebEnginePage::loadFinished,
155 &menuCallback_, &MenuCallback::cleanUpActions);
156
157 connect(&desktopInfo(), &DesktopInfo::fixedWidthFontListChanged, [this]() {
158 QString js = QStringLiteral(
159 "if (typeof window.onFontListReady === 'function') window.onFontListReady()");
160 this->webPage()->runJavaScript(js);
161 });
162
163 connect(qApp, SIGNAL(commitDataRequest(QSessionManager&)),
164 this, SLOT(commitDataRequest(QSessionManager&)),
165 Qt::DirectConnection);
166
167 setWindowIcon(QIcon(QString::fromUtf8(":/icons/RStudio.ico")));
168
169 setWindowTitle(desktop::activation().editionName());
170
171 #ifdef Q_OS_MAC
172 auto* pDefaultMenu = new QMenuBar(this);
173 pDefaultMenu->addMenu(new WindowMenu());
174 #endif
175
176 desktop::enableFullscreenMode(this, true);
177
178 Error error = pLauncher_->initialize();
179 if (error)
180 {
181 LOG_ERROR(error);
182 showError(nullptr,
183 QStringLiteral("Initialization error"),
184 QStringLiteral("Could not initialize Job Launcher"),
185 QString());
186 ::_exit(EXIT_FAILURE);
187 }
188 }
189
isRemoteDesktop() const190 bool MainWindow::isRemoteDesktop() const
191 {
192 return isRemoteDesktop_;
193 }
194
getSumatraPdfExePath()195 QString MainWindow::getSumatraPdfExePath()
196 {
197 return desktopInfo().getSumatraPdfExePath();
198 }
199
launchSession(bool reload)200 void MainWindow::launchSession(bool reload)
201 {
202 // we're about to start another session, so clear the workbench init flag
203 // (will get set again once the new session has initialized the workbench)
204 workbenchInitialized_ = false;
205
206 Error error = pSessionLauncher_->launchNextSession(reload);
207 if (error)
208 {
209 LOG_ERROR(error);
210
211 showMessageBox(QMessageBox::Critical,
212 this,
213 desktop::activation().editionName(),
214 QString::fromUtf8("The R session failed to start."),
215 QString());
216
217 quit();
218 }
219 }
220
launchRStudio(const std::vector<std::string> & args,const std::string & initialDir)221 void MainWindow::launchRStudio(const std::vector<std::string> &args,
222 const std::string& initialDir)
223 {
224 pAppLauncher_->launchRStudio(args, initialDir);
225 }
226
saveRemoteAuthCookies(const boost::function<QList<QNetworkCookie> ()> & loadCookies,const boost::function<void (QList<QNetworkCookie>)> & saveCookies,bool saveSessionCookies)227 void MainWindow::saveRemoteAuthCookies(const boost::function<QList<QNetworkCookie>()>& loadCookies,
228 const boost::function<void(QList<QNetworkCookie>)>& saveCookies,
229 bool saveSessionCookies)
230 {
231 std::map<std::string, QNetworkCookie> cookieMap;
232 auto addCookie = [&](const QNetworkCookie& cookie)
233 {
234 // ensure we don't save expired cookies
235 // due to how cookie domains are fluid, it's possible
236 // we could continue to save expired cookies if we don't filter them out
237 if (!cookie.isSessionCookie() && cookie.expirationDate().toUTC() <= QDateTime::currentDateTimeUtc())
238 return;
239
240 // also do not save the cookie if it is a session cookie and we were not told to explicitly save them
241 if (!saveSessionCookies && cookie.isSessionCookie())
242 return;
243
244 for (const auto& sessionServer : sessionServerSettings().servers())
245 {
246 if (sessionServer.cookieBelongs(cookie))
247 {
248 cookieMap[sessionServer.label() + "." + cookie.name().toStdString()] = cookie;
249 }
250 }
251 };
252
253 // merge with existing cookies on disk
254 QList<QNetworkCookie> cookies = loadCookies();
255 for (const QNetworkCookie& cookie : cookies)
256 {
257 addCookie(cookie);
258 }
259
260 if (pRemoteSessionLauncher_)
261 {
262 std::map<std::string, QNetworkCookie> remoteCookies = pRemoteSessionLauncher_->getCookies();
263
264 if (!remoteCookies.empty())
265 {
266 for (const auto& pair : remoteCookies)
267 {
268 addCookie(pair.second);
269 }
270 }
271 else
272 {
273 // cookies were empty, meaning they needed to be cleared (for example, due to sign out)
274 // ensure that all cookies for the domain are cleared out
275 for (auto it = cookieMap.cbegin(); it != cookieMap.cend();)
276 {
277 if (pRemoteSessionLauncher_->sessionServer().cookieBelongs(it->second))
278 {
279 it = cookieMap.erase(it);
280 }
281 else
282 {
283 ++it;
284 }
285 }
286 }
287 }
288
289 if (pLauncher_)
290 {
291 std::map<std::string, QNetworkCookie> cookies = pLauncher_->getCookies();
292 for (const auto& pair : cookies)
293 {
294 addCookie(pair.second);
295 }
296 }
297
298 QList<QNetworkCookie> mergedCookies;
299 for (const auto& pair: cookieMap)
300 {
301 mergedCookies.push_back(pair.second);
302 }
303
304 saveCookies(mergedCookies);
305 }
306
launchRemoteRStudio()307 void MainWindow::launchRemoteRStudio()
308 {
309 saveRemoteAuthCookies(boost::bind(&Options::tempAuthCookies, &options()),
310 boost::bind(&Options::setTempAuthCookies, &options(), _1),
311 true);
312
313 std::vector<std::string> args;
314 args.push_back(kSessionServerOption);
315 args.push_back(pRemoteSessionLauncher_->sessionServer().url());
316 args.push_back(kTempCookiesOption);
317
318 pAppLauncher_->launchRStudio(args);
319 }
320
launchRemoteRStudioProject(const QString & projectUrl)321 void MainWindow::launchRemoteRStudioProject(const QString& projectUrl)
322 {
323 saveRemoteAuthCookies(boost::bind(&Options::tempAuthCookies, &options()),
324 boost::bind(&Options::setTempAuthCookies, &options(), _1),
325 true);
326
327 std::vector<std::string> args;
328 args.push_back(kSessionServerOption);
329 args.push_back(pRemoteSessionLauncher_->sessionServer().url());
330 args.push_back(kSessionServerUrlOption);
331 args.push_back(projectUrl.toStdString());
332 args.push_back(kTempCookiesOption);
333
334 pAppLauncher_->launchRStudio(args);
335 }
336
workbenchInitialized()337 bool MainWindow::workbenchInitialized()
338 {
339 return workbenchInitialized_;
340 }
341
setErrorDisplayed()342 void MainWindow::setErrorDisplayed()
343 {
344 LOCK_MUTEX(mutex_)
345 {
346 isErrorDisplayed_ = true;
347 }
348 END_LOCK_MUTEX
349 }
350
onWorkbenchInitialized()351 void MainWindow::onWorkbenchInitialized()
352 {
353 //QTimer::singleShot(300, this, SLOT(resetMargins()));
354
355 // reset state (in case this occurred in response to a manual reload
356 // or reload for a new project context)
357 quitConfirmed_ = false;
358 geometrySaved_ = false;
359 workbenchInitialized_ = true;
360
361 webPage()->runJavaScript(
362 QStringLiteral("window.desktopHooks.getActiveProjectDir()"),
363 [&](QVariant qProjectDir)
364 {
365 QString projectDir = qProjectDir.toString();
366
367 if (projectDir.length() > 0)
368 {
369 setWindowTitle(tr("%1 - %2").arg(projectDir).arg(desktop::activation().editionName()));
370 DockTileView::setLabel(projectDir);
371 }
372 else
373 {
374 setWindowTitle(desktop::activation().editionName());
375 DockTileView::setLabel(QString());
376 }
377
378 avoidMoveCursorIfNecessary();
379 });
380
381 #ifdef Q_OS_MAC
382 webView()->setFocus();
383 #endif
384 }
385
resetMargins()386 void MainWindow::resetMargins()
387 {
388 setContentsMargins(0, 0, 0, 0);
389 }
390
391 // this notification occurs when windows or X11 is shutting
392 // down -- in this case we want to be a good citizen and just
393 // exit right away so we notify the gwt callback that a legit
394 // quit and exit is on the way and we set the quitConfirmed_
395 // flag so no prompting occurs (note that source documents
396 // have already been auto-saved so will be restored next time
397 // the current project context is opened)
commitDataRequest(QSessionManager & manager)398 void MainWindow::commitDataRequest(QSessionManager &manager)
399 {
400 gwtCallback_.setPendingQuit(PendingQuitAndExit);
401 quitConfirmed_ = true;
402 }
403
loadUrl(const QUrl & url)404 void MainWindow::loadUrl(const QUrl& url)
405 {
406 webView()->setBaseUrl(url);
407 webView()->load(url);
408 }
409
loadRequest(const QWebEngineHttpRequest & request)410 void MainWindow::loadRequest(const QWebEngineHttpRequest& request)
411 {
412 webView()->setBaseUrl(request.url());
413 webView()->load(request);
414 }
415
loadHtml(const QString & html)416 void MainWindow::loadHtml(const QString& html)
417 {
418 webView()->setHtml(html);
419 }
420
getPageProfile()421 QWebEngineProfile* MainWindow::getPageProfile()
422 {
423 return webView()->page()->profile();
424 }
425
quit()426 void MainWindow::quit()
427 {
428 RCommandEvaluator::setMainWindow(nullptr);
429 quitConfirmed_ = true;
430 close();
431 }
432
invokeCommand(QString commandId)433 void MainWindow::invokeCommand(QString commandId)
434 {
435 #ifdef Q_OS_MAC
436 QString fmt = QStringLiteral(R"EOF(
437 var wnd;
438 try {
439 wnd = window.$RStudio.last_focused_window;
440 } catch (e) {
441 wnd = window;
442 }
443 (wnd || window).desktopHooks.invokeCommand('%1');
444 )EOF");
445 #else
446 QString fmt = QStringLiteral("window.desktopHooks.invokeCommand('%1')");
447 #endif
448
449 QString command = fmt.arg(commandId);
450 webPage()->runJavaScript(command);
451 }
452
runJavaScript(QString script)453 void MainWindow::runJavaScript(QString script)
454 {
455 webPage()->runJavaScript(script);
456 }
457
458 namespace {
459
closeAllSatellites(QWidget * mainWindow)460 void closeAllSatellites(QWidget* mainWindow)
461 {
462 for (auto window: QApplication::topLevelWidgets())
463 if (window != mainWindow)
464 window->close();
465 }
466
467 } // end anonymous namespace
468
onSessionQuit()469 void MainWindow::onSessionQuit()
470 {
471 if (isRemoteDesktop_)
472 {
473 int pendingQuit = collectPendingQuitRequest();
474 if (pendingQuit == PendingQuitAndExit || quitConfirmed_)
475 {
476 closeAllSatellites(this);
477 quit();
478 }
479 }
480 }
481
closeEvent(QCloseEvent * pEvent)482 void MainWindow::closeEvent(QCloseEvent* pEvent)
483 {
484 #ifdef _WIN32
485 if (eventHook_)
486 ::UnhookWinEvent(eventHook_);
487 #endif
488
489 desktopInfo().onClose();
490 saveRemoteAuthCookies(boost::bind(&Options::authCookies, &options()),
491 boost::bind(&Options::setAuthCookies, &options(), _1),
492 false);
493
494 if (!geometrySaved_)
495 {
496 desktop::options().saveMainWindowBounds(this);
497 geometrySaved_ = true;
498 }
499
500 CloseServerSessions close = sessionServerSettings().closeServerSessionsOnExit();
501
502 if (quitConfirmed_ ||
503 (!isRemoteDesktop_ && pCurrentSessionProcess_ == nullptr) ||
504 (!isRemoteDesktop_ && pCurrentSessionProcess_->state() != QProcess::Running))
505 {
506 closeAllSatellites(this);
507 pEvent->accept();
508 return;
509 }
510
511 auto quit = [this]()
512 {
513 closeAllSatellites(this);
514 this->quit();
515 };
516
517 pEvent->ignore();
518 webPage()->runJavaScript(
519 QStringLiteral("!!window.desktopHooks"),
520 [=](QVariant hasQuitR) {
521
522 if (!hasQuitR.toBool())
523 {
524 LOG_ERROR_MESSAGE("Main window closed unexpectedly");
525
526 // exit to avoid user having to kill/force-close the application
527 quit();
528 }
529 else
530 {
531 if (!isRemoteDesktop_ ||
532 close == CloseServerSessions::Always)
533 {
534 webPage()->runJavaScript(
535 QStringLiteral("window.desktopHooks.quitR()"),
536 [=](QVariant ignored)
537 {
538 quitConfirmed_ = true;
539 });
540 }
541 else if (close == CloseServerSessions::Never)
542 {
543 quit();
544 }
545 else
546 {
547 webPage()->runJavaScript(
548 QStringLiteral("window.desktopHooks.promptToQuitR()"),
549 [=](QVariant ignored)
550 {
551 quitConfirmed_ = true;
552 });
553 }
554 }
555 });
556 }
557
setMenuBar(QMenuBar * pMenubar)558 void MainWindow::setMenuBar(QMenuBar *pMenubar)
559 {
560 delete menuBar();
561 this->QMainWindow::setMenuBar(pMenubar);
562 }
563
openFileInRStudio(QString path)564 void MainWindow::openFileInRStudio(QString path)
565 {
566 QFileInfo fileInfo(path);
567 if (!fileInfo.isAbsolute() || !fileInfo.exists() || !fileInfo.isFile())
568 return;
569
570 path = path.replace(QString::fromUtf8("\\"), QString::fromUtf8("\\\\"))
571 .replace(QString::fromUtf8("\""), QString::fromUtf8("\\\""))
572 .replace(QString::fromUtf8("\n"), QString::fromUtf8("\\n"));
573
574 webView()->page()->runJavaScript(
575 QString::fromUtf8("window.desktopHooks.openFile(\"") + path + QString::fromUtf8("\")"));
576 }
577
onPdfViewerClosed(QString pdfPath)578 void MainWindow::onPdfViewerClosed(QString pdfPath)
579 {
580 webView()->page()->runJavaScript(
581 QString::fromUtf8("window.synctexNotifyPdfViewerClosed(\"") +
582 pdfPath + QString::fromUtf8("\")"));
583 }
584
onPdfViewerSyncSource(QString srcFile,int line,int column)585 void MainWindow::onPdfViewerSyncSource(QString srcFile, int line, int column)
586 {
587 boost::format fmt("window.desktopSynctexInverseSearch(\"%1%\", %2%, %3%)");
588 std::string js = boost::str(fmt % srcFile.toStdString() % line % column);
589 webView()->page()->runJavaScript(QString::fromStdString(js));
590 }
591
onLicenseLost(QString licenseMessage)592 void MainWindow::onLicenseLost(QString licenseMessage)
593 {
594 webView()->page()->runJavaScript(
595 QString::fromUtf8("window.desktopHooks.licenseLost('") + licenseMessage +
596 QString::fromUtf8("');"));
597 }
598
onUpdateLicenseWarningBar(QString message)599 void MainWindow::onUpdateLicenseWarningBar(QString message)
600 {
601 webView()->page()->runJavaScript(
602 QString::fromUtf8("window.desktopHooks.updateLicenseWarningBar('") + message +
603 QString::fromUtf8("');"));
604 }
605
606 // private interface for SessionLauncher
607
setSessionLauncher(SessionLauncher * pSessionLauncher)608 void MainWindow::setSessionLauncher(SessionLauncher* pSessionLauncher)
609 {
610 pSessionLauncher_ = pSessionLauncher;
611 }
612
getRemoteDesktopSessionLauncher()613 RemoteDesktopSessionLauncher* MainWindow::getRemoteDesktopSessionLauncher()
614 {
615 return pRemoteSessionLauncher_;
616 }
617
getJobLauncher()618 boost::shared_ptr<JobLauncher> MainWindow::getJobLauncher()
619 {
620 return pLauncher_;
621 }
622
setRemoteDesktopSessionLauncher(RemoteDesktopSessionLauncher * pSessionLauncher)623 void MainWindow::setRemoteDesktopSessionLauncher(RemoteDesktopSessionLauncher* pSessionLauncher)
624 {
625 pRemoteSessionLauncher_ = pSessionLauncher;
626 }
627
setSessionProcess(QProcess * pSessionProcess)628 void MainWindow::setSessionProcess(QProcess* pSessionProcess)
629 {
630 pCurrentSessionProcess_ = pSessionProcess;
631
632 // when R creates dialogs (e.g. through utils::askYesNo), their first
633 // invocation might show behind the RStudio window. this allows us
634 // to detect when those Windows are opened and focused, and raise them
635 // to the front.
636 #ifdef _WIN32
637 if (eventHook_)
638 ::UnhookWinEvent(eventHook_);
639
640 if (pSessionProcess)
641 {
642 eventHook_ = ::SetWinEventHook(
643 EVENT_SYSTEM_DIALOGSTART, EVENT_SYSTEM_DIALOGSTART,
644 nullptr,
645 onDialogStart,
646 pSessionProcess->processId(),
647 0,
648 WINEVENT_OUTOFCONTEXT);
649 }
650 #endif
651
652 }
653
setAppLauncher(ApplicationLaunch * pAppLauncher)654 void MainWindow::setAppLauncher(ApplicationLaunch *pAppLauncher)
655 {
656 pAppLauncher_ = pAppLauncher;
657 }
658
659 // allow SessionLauncher to collect restart requests from GwtCallback
collectPendingQuitRequest()660 int MainWindow::collectPendingQuitRequest()
661 {
662 return gwtCallback_.collectPendingQuitRequest();
663 }
664
desktopHooksAvailable()665 bool MainWindow::desktopHooksAvailable()
666 {
667 return desktopInfo().desktopHooksAvailable();
668 }
669
onActivated()670 void MainWindow::onActivated()
671 {
672 }
673
onUrlChanged(QUrl url)674 void MainWindow::onUrlChanged(QUrl url)
675 {
676 urlChanged(url);
677 }
678
reload()679 void MainWindow::reload()
680 {
681 s_reloadCount++;
682 loadUrl(webView()->baseUrl());
683 }
684
onLoadFinished(bool ok)685 void MainWindow::onLoadFinished(bool ok)
686 {
687 LOCK_MUTEX(mutex_)
688 {
689 if (ok)
690 {
691 // we've successfully loaded!
692 }
693 else if (isErrorDisplayed_)
694 {
695 // the session failed to launch and we're already showing
696 // an error page to the user; nothing else to do here.
697 }
698 else
699 {
700 if (s_reloadCount < s_maxReloadCount)
701 {
702 // the load failed, but we haven't yet received word that the
703 // session has failed to load. let the user know that the R
704 // session is still initializing, and then reload the page.
705 std::map<std::string, std::string> vars = {};
706
707 std::ostringstream oss;
708 Error error = text::renderTemplate(
709 options().resourcesPath().completePath("html/loading.html"),
710 vars,
711 oss);
712
713 if (error)
714 LOG_ERROR(error);
715
716 loadHtml(QString::fromStdString(oss.str()));
717
718 QTimer::singleShot(
719 s_reloadWaitDuration * s_reloadCount,
720 [&]() { reload(); });
721 }
722 else
723 {
724 s_reloadCount = 0;
725 onLoadFailed();
726 }
727 }
728 }
729 END_LOCK_MUTEX
730 }
731
onLoadFailed()732 void MainWindow::onLoadFailed()
733 {
734 if (pRemoteSessionLauncher_ || isErrorDisplayed_)
735 return;
736
737 std::map<std::string, std::string> vars = {
738 { "url", webView()->baseUrl().toString().toStdString() }
739 };
740
741 std::ostringstream oss;
742 Error error = text::renderTemplate(
743 options().resourcesPath().completePath("html/connect.html"),
744 vars,
745 oss);
746
747 if (error)
748 {
749 LOG_ERROR(error);
750 return;
751 }
752
753 loadHtml(QString::fromStdString(oss.str()));
754 }
755
getWebView()756 WebView* MainWindow::getWebView()
757 {
758 return webView();
759 }
760
761 } // namespace desktop
762 } // namespace rstudio
763