#include "dialog.h" #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "unistd.h" #include "stdio.h" #include "sys/wait.h" #include "ui_dialog.h" #include "sessiontreeitem.h" #include "globals.h" #include "vsession.h" #include "userconfig.h" #include "filedownloader.h" #include "windowmanager.h" static bool isProcessRunning(const QString &binary); static QDomDocument toDomDocument(const QString& what, const QByteArray& data, const QString& backupFile, const QString& mandatoryChild); Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog) { model_[TAB_NATIVE] = new SessionTreeModel(parent); model_[TAB_RECENT_COURSES] = new SessionTreeModel(parent, true); model_[TAB_ALL_VMS] = new SessionTreeModel(parent); userInteracted_ = false; genericExpandedOnce_ = false; autoQuit_ = g_autoQuitSeconds; ui->setupUi(this); tabs_[TAB_NATIVE] = ui->tabButtonLocal; tabs_[TAB_RECENT_COURSES] = ui->tabButtonMyClasses; tabs_[TAB_ALL_VMS] = ui->tabButtonAllClasses; strings_[STR_LOADING] = QCoreApplication::instance()->translate("Dialog", "Loading..."); strings_[STR_URL_ERROR] = QCoreApplication::instance()->translate("Dialog", "URL Error"); strings_[STR_NO_ITEMS] = QCoreApplication::instance()->translate("Dialog", "No Items"); // Re-center dialog every second to account for resolution changes QRect desktopRect = QApplication::desktop()->availableGeometry(this); oldCenter_ = desktopRect.center(); centerTimer_ = new QTimer(this); connect(centerTimer_, &QTimer::timeout, this, &Dialog::onCenterTimer); centerTimer_->start(1000); ui->treeView->setFocusProxy(ui->filterEdit); // ui->filterEdit->installEventFilter(this); QCoreApplication::instance()->installEventFilter(this); if (!UserConfig::isNewsHelpOpen()) { ui->helpBox->hide(); ui->newsBox->hide(); } ui->lblAutoQuit->hide(); this->addStatusString(STR_LOADING); /* * TODO: why connect signal/slot when no item was loaded yet? * TODO: Change "TODO" in the above line to "QUESTION", as it's just that QObject::connect(ui->treeView->selectionModel(), SIGNAL(currentChanged ( const QModelIndex&, const QModelIndex&)), this, SLOT(treeView_selectionChanged(const QModelIndex&, const QModelIndex&))); */ ui->PVS_checkbox->setVisible(Config::isSet(Config::PVS)); ui->PVS_checkbox->setChecked(Config::isSet(Config::PVS_CHECKED)); ui->chkNoScreenSaver->setVisible(Config::isSet(Config::ALLOW_SCREENSAVER_DISABLE)); activeTab_ = -1; if (Config::isSet(Config::EXAM_MODE)) { ui->tabButtonLocal->setEnabled(false); this->onTabButtonChanged(TAB_ALL_VMS); /* modify the pvs checkbox */ ui->PVS_checkbox->setText(tr("join PVS(limited)")); ui->PVS_checkbox->setEnabled(false); ui->PVS_checkbox->setChecked(true); } else { this->onTabButtonChanged(TAB_NATIVE); } ui->chkAdminMode->setVisible(Config::isSet(Config::ALLOW_VM_EDIT)); ui->chkAdminMode->setEnabled(false); ui->btnScreenSetup->setVisible(isProcessRunning("beamergui")); if (QApplication::screens().size() > 1) { QFont copy = ui->btnScreenSetup->font(); copy.setBold(true); ui->btnScreenSetup->setFont(copy); } // TODO: Implement bug report dialog :) ui->buttonBugReport->setEnabled(false); QObject::connect(SessionsIconHolder::get(), &SessionsIconHolder::iconDownloaded, this, &Dialog::iconDownloaded); } Dialog::~Dialog() { delete ui; } void Dialog::changeEvent(QEvent *e) { QDialog::changeEvent(e); if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); } } void Dialog::on_treeView_doubleClicked(const QModelIndex& index) { userInteracted_ = true; // this method gets called when a Session has been selected to start SessionTreeItem* item = static_cast(index.internalPointer()); if (item == nullptr) return; // do nothing if cast failed const Session* s(item->session()); if (s == nullptr) // no valid session has been selected, do nothing return; // Check preconditions. The method might show error messages and return false if (!s->canRun(true)) return; // These two are up here in case run-virt cares... if (Config::isSet(Config::PVS)) { if (ui->PVS_checkbox->isChecked()) { setenv("PVS_AUTO_CONNECT", "TRUE", 1); } else { setenv("PVS_AUTO_CONNECT", "FALSE", 1); } } if (ui->chkAdminMode->isEnabled() && ui->chkAdminMode->isChecked()) { setenv("VMCHOOSER_ADMIN_MODE", "TRUE", 1); } else { setenv("VMCHOOSER_ADMIN_MODE", "FALSE", 1); } if (ui->chkNoScreenSaver->isChecked()) { setenv("VMCHOOSER_DISABLE_SCREENSAVER", "TRUE", 1); } else { setenv("VMCHOOSER_DISABLE_SCREENSAVER", "FALSE", 1); } // Run session if (s->run()) { WindowManager::stopOwnInstance(); // Run session start script if the session could be initialized successfully if (QFile::exists(SESSION_START_SCRIPT)) { // Use the current environment variables and add the necessary // information for the startUpScipt. // export session information to the environment of the process to be exec'ed setenv("SESSION_NAME", s->shortDescription().toUtf8(), 1); setenv("SESSION_UUID", s->uuid().toUtf8(), 1); setenv("SESSION_CMD", s->execCommand().toUtf8(), 1); if (s->type() == Session::VSESSION) { setenv("SESSION_TYPE", "VSESSION", 1); } else if (s->type() == Session::XSESSION) { setenv("SESSION_TYPE", "XSESSION", 1); } // Fork this process twice to detach pid_t pid = fork(); if (pid == 0) { // Close all filedescriptors for (int i = 3; i < 1024; ++i) ::close(i); // Change process group setsid(); // Reopen standard pipes freopen("/dev/null", "r", stdin); freopen("/dev/null", "w", stdout); freopen("/dev/null", "w", stderr); pid = fork(); if (pid == 0) { // At this point we are executing as the 2nd child process // Replace this clone with the process we'd like to start char * const args[] = { strdup(VMCHOOSER_SESSION_START_SCRIPT), nullptr }; execv(VMCHOOSER_SESSION_START_SCRIPT, args); } _exit(0); // Dont use exit hooks } // Wait and cleanup the intermediate process waitpid(pid, nullptr, 0); } UserConfig::addLastSession(s->uuid().isEmpty() ? s->shortDescription() : s->uuid()); UserConfig::setLastTab(activeTab_); UserConfig::setNewsHelpOpen(!ui->helpBox->isHidden()); centerTimer_->stop(); // Stop the auto-center/auto-quit timer, so we don't kill the session :> setVisible(false); } else { QMessageBox::critical( this, trUtf8("vmchooser"), trUtf8("Vmchooser failed to run the selected session!")); } } void Dialog::on_treeView_expanded(const QModelIndex& index) { if (activeTab_ != TAB_ALL_VMS) return; SessionTreeItem* item = static_cast(index.internalPointer()); if (item->session() == nullptr && item->sectionType() != SECTION_FOR_LOCATION) { genericExpandedOnce_ = true; } } void Dialog::addItems(const QList& entries, int tab) { if (tab < 0 || tab >= TAB_COUNT) return; // Invalid index if (Config::isSet(Config::EXAM_MODE) && tab == TAB_NATIVE) return; // Exam mode -- keep native sessions empty ui->treeView->blockSignals(true); if (tab != TAB_RECENT_COURSES) { // If tab is not the recent courses tab, check the list for any recent sessions auto prev = UserConfig::getLastSessions(); if (!prev.isEmpty()) { QList matches; for (auto it : entries) { if ((!it->uuid().isEmpty() && prev.contains(it->uuid())) || prev.contains(it->shortDescription())) { matches.append(it); } // Also check for validity it->checkCanRun(); } if (!matches.isEmpty()) { addItems(matches, TAB_RECENT_COURSES); ui->treeView->blockSignals(true); } } } this->model_[tab]->addItems(entries); tabs_[tab]->setEnabled(this->model_[tab]->rowCount() != 0); if (tab == activeTab_) { setListModel(this->model_[tab]); } ui->treeView->blockSignals(false); model_[tab]->updateView(); if (!ui->filterEdit->text().isEmpty()) { on_filterEdit_textChanged(); } if (tab != TAB_RECENT_COURSES) { selectPreviousSession(); } } void Dialog::addStatusString(const int status) { if (status < 0 || status >= STR__MAX) return; this->model_[TAB_RECENT_COURSES]->addLabelItem(strings_[status]); this->model_[TAB_ALL_VMS]->addLabelItem(strings_[status]); tabs_[TAB_RECENT_COURSES]->setEnabled(this->model_[TAB_RECENT_COURSES]->rowCount() > 1); tabs_[TAB_ALL_VMS]->setEnabled(this->model_[TAB_ALL_VMS]->rowCount() > 1); model_[TAB_RECENT_COURSES]->updateView(); model_[TAB_ALL_VMS]->updateView(); } void Dialog::removeStatusString(const int status) { if (status < 0 || status >= STR__MAX) return; this->model_[TAB_RECENT_COURSES]->removeItem(strings_[status]); this->model_[TAB_ALL_VMS]->removeItem(strings_[status]); tabs_[TAB_RECENT_COURSES]->setEnabled(this->model_[TAB_RECENT_COURSES]->rowCount() > 1); tabs_[TAB_ALL_VMS]->setEnabled(this->model_[TAB_RECENT_COURSES]->rowCount() > 1); model_[TAB_RECENT_COURSES]->updateView(); model_[TAB_ALL_VMS]->updateView(); } void Dialog::on_pushButtonAbort_clicked() { close(); } void Dialog::on_pushButtonStart_clicked() { userInteracted_ = true; this->on_treeView_doubleClicked(ui->treeView->selectionModel()->currentIndex()); } void Dialog::on_btnScreenSetup_clicked() { QProcess::startDetached("beamergui", QStringList("-w")); } bool Dialog::selectSession(const QString& name, int preferredTab) { QModelIndex root(ui->treeView->rootIndex()); int bestTab = -1; QModelIndex bestIndex; for (int tab = TAB_COUNT - 1; tab >= 0; --tab) { if (bestTab != -1 && preferredTab != tab) // We already have a potential match, only keep going if this is the desired tab continue; for (int i = 0; i < model_[tab]->rowCount(root); ++i) { QModelIndex section(model_[tab]->index(i, 0, root)); if (!section.isValid()) continue; for (int j = 0; j < model_[tab]->rowCount(section); ++j) { QModelIndex index(model_[tab]->index(j, 0, section)); if (!index.isValid()) continue; SessionTreeItem* item = static_cast(index.internalPointer()); const Session* s = item->session(); if (s == nullptr) { continue; } if ((!s->uuid().isEmpty() && s->uuid() == name) || s->shortDescription() == name) { bestTab = tab; bestIndex = index; break; // Break inner, keep checking other tabs } } } } if (bestTab != -1) { // change the tab qDebug() << "Best tab is" << bestTab; onTabButtonChanged(bestTab); // set selection ui->treeView->selectionModel()->clearSelection(); ui->treeView->selectionModel()->clear(); ui->treeView->selectionModel()->select(bestIndex, QItemSelectionModel::Select); ui->treeView->selectionModel()->setCurrentIndex(bestIndex, QItemSelectionModel::Select); ui->treeView->scrollTo(bestIndex); return true; } return false; } void Dialog::selectPreviousSession() { if (userInteracted_) { qDebug() << "Not selecting previous session as user interacted or session was already selected"; return; } int lastTab = UserConfig::getLastTab(); QString lastSession = Config::get(Config::DEFAULT_SESSION); if (lastSession.isEmpty()) { auto list = UserConfig::getLastSessions(); if (!list.isEmpty()) { lastSession = list.back(); } } if (!lastSession.isEmpty()) { qDebug() << "Trying to select last session: " << lastSession; ui->treeView->clearSelection(); if (selectSession(lastSession, lastTab)) { qDebug() << "Success"; userInteracted_ = true; return; } } // could not find last session, change to last used tab if (lastTab >= 0 && lastTab < TAB_COUNT) { qDebug() << "Trying to select last tab " << lastTab; this->onTabButtonChanged(lastTab); } else { int defaultTab = Config::get(Config::DEFAULT_TAB).toInt(); qDebug() << "Selected default tab " << defaultTab; // Select default tab this->onTabButtonChanged(defaultTab); } } void Dialog::startSession(const QString& name) { autoStartEntry_ = name; } void Dialog::setTheme() { QString label_l_style, label_r_style; QString backgroundColor, imageLeft, imageRight; QString themePathBase, themePathIni, themePathImgLeft, themePathImgRight; if (!Config::isSet(Config::THEME)) return; QString theme(Config::get(Config::THEME)); themePathBase = QString("%1/%2/").arg(VMCHOOSER_THEME_BASE).arg(theme); themePathIni = QString("%1%2.ini").arg(themePathBase).arg(theme); if (!QFile::exists(themePathIni)) { qDebug() << "Theme config" << themePathIni << "does not exist"; return; } QSettings themeSettings(themePathIni, QSettings::IniFormat); backgroundColor = themeSettings.value("background-color").toString(); imageLeft = themeSettings.value("image-left").toString(); imageRight = themeSettings.value("image-right").toString(); themePathImgLeft = QString("%1%2").arg(themePathBase).arg(imageLeft); themePathImgRight = QString("%1%2").arg(themePathBase).arg(imageRight); QRegExp re("(.*background-color:)#[^;]*(;.*)"); ui->label_l->setPixmap(QPixmap(themePathImgLeft)); ui->label_r->setPixmap(QPixmap(themePathImgRight)); label_l_style = ui->label_l->styleSheet(); label_r_style = ui->label_r->styleSheet(); backgroundColor.prepend("\\1").append("\\2"); label_l_style.replace(re, backgroundColor); label_r_style.replace(re, backgroundColor); ui->label_l->setStyleSheet(label_l_style); ui->label_r->setStyleSheet(label_r_style); } void Dialog::onCenterTimer() { // center dialog on primary screen if (isVisible()) { QRect desktopRect = QApplication::desktop()->availableGeometry(this); QPoint center = desktopRect.center(); if (center != oldCenter_) { if (g_fullscreen) this->resize(desktopRect.width(), desktopRect.height()); this->move(center.x() - this->width() / 2, center.y() - this->height() / 2); oldCenter_ = center; } } // Handle auto-quit timeout if (autoQuit_ > 0) { autoQuit_--; if (autoQuit_ == 0) { QCoreApplication::instance()->exit(0); } else if (autoQuit_ < 60) { ui->lblAutoQuit->setText(trUtf8("Auto logout in %1").arg(autoQuit_)); ui->lblAutoQuit->show(); } else if (!ui->lblAutoQuit->isHidden()) { ui->lblAutoQuit->hide(); } } } /** * Download lecture list, news and help */ void Dialog::downloadData(const QString& locationIds) { QUrl listUrl(Config::isSet(Config::URL_LIST) ? Config::get(Config::URL_LIST) : Config::get(Config::URL_BASE).append("/list")); QUrlQuery listQuery(listUrl); if (!locationIds.isEmpty()) { listQuery.addQueryItem("locations", locationIds); } if (Config::isSet(Config::EXAM_MODE)) { listQuery.addQueryItem("exams", "exam-mode"); } listUrl.setQuery(listQuery); // // Download lecture XML FileDownloader::download(listUrl, [this](QNetworkReply::NetworkError err, const QByteArray& data) { QList sessions; QDomDocument doc = toDomDocument(QStringLiteral("lecture list"), data, TEMP_PATH_XML_LIST, QStringLiteral("settings")); sessions = VSession::loadFromXmlDocument(doc); this->removeStatusString(STR_LOADING); if (sessions.isEmpty()) { if (err == QNetworkReply::NoError) { this->addStatusString(STR_NO_ITEMS); } else { this->addStatusString(STR_URL_ERROR); } } else { qSort(sessions.begin(), sessions.end(), sessionComparator); this->addItems(sessions, TAB_ALL_VMS); bool showEdit = false; // Only show edit button if at least one lecture is editable for (QList::const_iterator it = sessions.begin(); it != sessions.end(); ++it) { if (reinterpret_cast(*it)->canEdit()) { showEdit = true; break; } } if (showEdit) { ui->chkAdminMode->setVisible(true); } } checkAutostart(); // select last-session selectPreviousSession(); userInteracted_ = true; }); // // News FileDownloader::download(QUrl(Config::isSet(Config::URL_NEWS) ? Config::get(Config::URL_NEWS) : Config::get(Config::URL_BASE).append("/news")), [this](QNetworkReply::NetworkError err, const QByteArray& data) { QDomDocument doc = toDomDocument(QStringLiteral("news"), data, TEMP_PATH_NEWS, QStringLiteral("news")); QDomElement newsNode = doc.firstChildElement("news"); QDomElement timeNode = newsNode.firstChildElement("date"); QDomElement infoNode = newsNode.firstChildElement("info"); QDomElement headlineNode = newsNode.firstChildElement("headline"); QDateTime timestamp; timestamp.setTime_t(timeNode.text().toUInt()); if (timeNode.isNull() || infoNode.isNull()) { qDebug() << "Could not load news. Network:" << err; ui->newsTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get news. (//news/date or //news/info missing)")); } else { // format and print news ui->newsTextBrowser->setText(QString("

" + headlineNode.text() + "

" + timestamp.toString(Qt::SystemLocaleShortDate) + "

" + infoNode.text() + "

")); } if (ui->helpBox->isHidden() && (UserConfig::getLastNewsTime() < timestamp.toTime_t() || UserConfig::getLastNewsTime() > QDateTime::currentMSecsSinceEpoch() / 1000)) { // show news if not seen before on_helpNewsButton_clicked(); } // update ini UserConfig::setLastNewsTime(timestamp.toTime_t()); }); // // Download help FileDownloader::download(QUrl(Config::isSet(Config::URL_HELP) ? Config::get(Config::URL_HELP) : Config::get(Config::URL_BASE).append("/help")), [this](QNetworkReply::NetworkError err, const QByteArray& data) { QDomDocument doc = toDomDocument(QStringLiteral("help"), data, TEMP_PATH_HELP, QStringLiteral("news")); QDomElement helpTextNode = doc.firstChildElement("news").firstChildElement("info"); if (helpTextNode.isNull()) { qDebug() << "Could not load help. Network:" << err; ui->helpTextBrowser->setText(QCoreApplication::instance()->translate("Dialog", "Could not get help (XML has no //news/info)")); } else { ui->helpTextBrowser->setText(helpTextNode.text()); } }); // } void Dialog::mousePressEvent(QMouseEvent * event) { QDialog::mousePressEvent(event); userInteracted_ = true; } void Dialog::treeView_selectionChanged(const QModelIndex& current, const QModelIndex&) { SessionTreeItem* item = static_cast(current.internalPointer()); if (item == nullptr) { ui->chkAdminMode->setEnabled(false); return; } const Session* s(item->session()); if (!s) { qDebug() << "invalid selection"; // no valid session has been selected, do nothing ui->chkAdminMode->setEnabled(false); return; } QString description; if (s->type() == Session::VSESSION) { const VSession* vs = reinterpret_cast(s); ui->label_creator->setText(vs->getAttribute("creator", "param")); ui->label_creator->setToolTip(vs->getAttribute("creator", "param")); ui->label_os->setText(vs->getAttribute("os_name", "param")); ui->label_os->setToolTip(vs->getAttribute("os_name", "param")); ui->label_platform->setText(vs->getAttribute("virtualizer_name", "param")); ui->label_platform->setToolTip(vs->getAttribute("virtualizer_name", "param")); // TODO: This is a bug? vs->canEdit() seems completely pointless right now... ui->chkAdminMode->setEnabled(vs->canEdit() || Config::isSet(Config::ALLOW_VM_EDIT)); if (vs->keywords().length() > 0) { description = "\n\nKeywords: "; for (int i = 0; i < vs->keywords().length(); ++i) { description += vs->keywords()[i] + ", "; } } } else { ui->label_creator->setText(""); ui->label_creator->setToolTip(""); ui->label_os->setText(QCoreApplication::instance()->translate("Dialog", "Native Linux")); ui->label_platform->setText("Linux"); ui->label_platform->setToolTip(""); } ui->label_name->setText(s->shortDescription()); ui->label_name->setToolTip(s->shortDescription()); ui->textBrowser->setPlainText(s->description() + description); } void Dialog::on_tabButtonLocal_clicked() { userInteracted_ = true; onTabButtonChanged(TAB_NATIVE); } void Dialog::on_tabButtonMyClasses_clicked() { userInteracted_ = true; onTabButtonChanged(TAB_RECENT_COURSES); } void Dialog::on_tabButtonAllClasses_clicked() { userInteracted_ = true; onTabButtonChanged(TAB_ALL_VMS); } void Dialog::onTabButtonChanged(int tab) { if (tab < 0 || tab >= TAB_COUNT) { // no valid button return; } // Block this so it won't trigger the expand signal while refilling, which would // interfere with LOCATION_EXCLUSIVE mode ui->treeView->blockSignals(true); // give focus to treeView if (!ui->treeView->hasFocus()) { ui->treeView->setFocus(); } // Update pressed status of buttons for (int i = 0; i < TAB_COUNT; ++i) { tabs_[i]->setChecked(tab == i); } // clear filter if necessary if (activeTab_ != tab) { this->ui->filterEdit->setText(""); } // load the new list setListModel(model_[tab]); this->activeTab_ = tab; ui->treeView->blockSignals(false); /* get the first element ad select it */ selectFirstElement(); } void Dialog::on_filterEdit_textEdited() { userInteracted_ = true; } void Dialog::on_filterEdit_textChanged() { if (activeTab_ < 0 || activeTab_ >= TAB_COUNT) return; SessionTreeModel *newModel = nullptr; // filter the current model if (!ui->filterEdit->text().isEmpty() && ui->filterEdit->text().replace(" ", "").length() > 2) { if (model_[activeTab_] != nullptr) { newModel = new SessionTreeModel(this); newModel->addItems(this->model_[activeTab_]->lookForItem(ui->filterEdit->text())); } } if (newModel == nullptr) { newModel = model_[activeTab_]; } if (newModel != nullptr) { setListModel(newModel); } } void Dialog::setListModel(SessionTreeModel *model) { QAbstractItemModel *old = nullptr; if (ui->treeView->model() == model_[TAB_NATIVE] || ui->treeView->model() == model_[TAB_RECENT_COURSES] || ui->treeView->model() == model_[TAB_ALL_VMS]) { // A default model is currently being set; don't delete it } else { // Remember currently set model so we can delete it after setting the new one old = ui->treeView->model(); } // Disconnect current model QObject::disconnect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &Dialog::treeView_selectionChanged); ui->treeView->setModel(model); ui->treeView->expandAll(); if (!genericExpandedOnce_ && g_forLocationHandling == LOCATION_EXCLUSIVE && model->rowCount() > 1) { QModelIndex index = model->getSection(SECTION_GENERIC); if (index.isValid()) { ui->treeView->collapse(index); } index = model->getSection(SECTION_TEMPLATES); if (index.isValid()) { ui->treeView->collapse(index); } } if (old != nullptr) { old->deleteLater(); } // reconnect the treeModel QObject::connect(ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &Dialog::treeView_selectionChanged); // select the first element, if no element is selected if (ui->treeView->selectionModel()->selectedRows().count() == 0) { //ui->treeView->selectionModel()->setCurrentIndex(ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()), QItemSelectionModel::Select); ui->treeView->selectionModel()->select(ui->treeView->model()->index(0, 0, ui->treeView->rootIndex()), QItemSelectionModel::Select); } } void Dialog::on_helpNewsButton_clicked() { if (!ui->helpBox->isHidden()) { ui->helpBox->hide(); ui->newsBox->hide(); } else { ui->helpBox->show(); ui->newsBox->show(); } } void Dialog::keyPressEvent(QKeyEvent* event) { switch(event->key()) { case Qt::Key_Return: this->on_pushButtonStart_clicked(); break; case Qt::Key_Escape: this->on_pushButtonAbort_clicked(); break; case Qt::Key_H: this->on_helpNewsButton_clicked(); break; default: break; } QDialog::keyPressEvent(event); } void Dialog::iconDownloaded(const QUrl& url, const QIcon&) { qDebug() << "Icon downloaded... (" << url << ")"; // TODO: Check which model(s) contain an entry with this icon model_[TAB_RECENT_COURSES]->updateView(); model_[TAB_ALL_VMS]->updateView(); } void Dialog::on_leftKey() { int i = activeTab_; do { i = (i - 1 + TAB_COUNT) % TAB_COUNT; } while (!tabs_[i]->isEnabled() && i != activeTab_); onTabButtonChanged(i); } void Dialog::on_rightKey() { int i = activeTab_; do { i = (i + 1) % TAB_COUNT; } while (!tabs_[i]->isEnabled() && i != activeTab_); onTabButtonChanged(i); } /* when the user presses the space key, the current selection in the treeview * should be collapsed/expanded. */ void Dialog::on_spaceKey() { QModelIndex index = ui->treeView->selectionModel()->currentIndex(); if (ui->treeView->isExpanded(index)) { ui->treeView->collapse(index); } else { ui->treeView->expand(index); } } void Dialog::selectFirstElement() { QModelIndex root(ui->treeView->rootIndex()); QModelIndex section(model_[activeTab_]->index(0, 0, root)); QModelIndex newIndex = QModelIndex(model_[activeTab_]->index(0, 0, section)); ui->treeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select); ui->treeView->setFocus(); } /* install this filter to the filterEdit to listen for arrow keys */ bool Dialog::eventFilter(QObject*, QEvent *event) { if (event->type() == QEvent::KeyPress) { autoQuit_ = g_autoQuitSeconds; QKeyEvent *keyEvent = static_cast(event); bool fortv = false; if (keyEvent->key() == Qt::Key_Right) { on_rightKey(); fortv = true; } if (keyEvent->key() == Qt::Key_Left) { on_leftKey(); fortv = true; } if (keyEvent->key() == Qt::Key_Up) { ui->treeView->cursorUp(); fortv = true; } if (keyEvent->key() == Qt::Key_Down) { ui->treeView->cursorDown(); fortv = true; } if (fortv) { ui->treeView->setFocus(); return true; } } else if (event->type() == QEvent::MouseMove) { autoQuit_ = g_autoQuitSeconds; } return false; } void Dialog::checkAutostart() { if (!autoStartEntry_.isEmpty()) { if (this->selectSession(autoStartEntry_)) { this->on_treeView_doubleClicked(ui->treeView->selectionModel()->currentIndex()); } else { QMessageBox::critical(this, "Autostart", QString::fromUtf8("Konnte %1 nicht starten.").arg(autoStartEntry_)); } autoStartEntry_.clear(); return; } } void Dialog::showEvent(QShowEvent *e) { QDialog::showEvent(e); // Make sure a window manager is running, so any modals or other popups work properly QTimer::singleShot(10, []() { WindowManager::ensureRunning(); }); } static bool isProcessRunning(const QString &binary) { bool full = binary.contains('/'); QDir proc("/proc"); for (auto entry : proc.entryInfoList(QStringList(), QDir::NoDotAndDotDot | QDir::AllDirs)) { if (!entry.isReadable()) continue; QDir dest = QDir(entry.filePath() + "/exe"); if (!dest.isReadable()) continue; QString wurst = dest.canonicalPath(); if (wurst.isEmpty()) continue; if (full && wurst == binary) return true; if (!full && wurst.mid(wurst.lastIndexOf('/') + 1) == binary) return true; } return false; } static QDomDocument toDomDocument(const QString& what, const QByteArray& data, const QString& backupFile, const QString& mandatoryChild) { QDomDocument doc; if (!data.isEmpty()) { if (!doc.setContent(data)) { qDebug() << "Downloaded" << what << "XML contains errors."; } } else { qDebug() << "No content downloaded for" << what; } QFile backup(backupFile); if (doc.isNull() || !doc.hasChildNodes()) { if (backup.open(QFile::ReadOnly)) { if (!doc.setContent(&backup)) { qDebug() << "Could not load" << what << "backup."; } } } if (doc.isNull() || !doc.hasChildNodes()) return QDomDocument(); // Above methods failed if (doc.firstChildElement(mandatoryChild).isNull()) { qDebug() << "Downloaded" << what << "xml doesn't contain mandatory root node" << mandatoryChild; return QDomDocument(); } if (!backup.isOpen()) { // If it were already open we'd have read the backup, so no need to write it out again... if (!backup.open(QFile::WriteOnly)) { qDebug() << "Cannot open" << what << "backup file" << backupFile << "for writing"; } else { backup.write(data); } } return doc; }