File indexing completed on 2024-03-24 05:49:30
0001 // SPDX-License-Identifier: GPL-3.0-or-later 0002 /* 0003 Copyright 2017 - 2023 Martin Koller, kollix@aon.at 0004 0005 This file is part of liquidshell. 0006 0007 liquidshell is free software: you can redistribute it and/or modify 0008 it under the terms of the GNU General Public License as published by 0009 the Free Software Foundation, either version 3 of the License, or 0010 (at your option) any later version. 0011 0012 liquidshell is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 GNU General Public License for more details. 0016 0017 You should have received a copy of the GNU General Public License 0018 along with liquidshell. If not, see <http://www.gnu.org/licenses/>. 0019 */ 0020 0021 #include <PkUpdateList.hxx> 0022 #include <DesktopWidget.hxx> 0023 0024 #include <QScrollBar> 0025 #include <QGridLayout> 0026 #include <QVBoxLayout> 0027 #include <QCheckBox> 0028 #include <QLineEdit> 0029 #include <QPushButton> 0030 #include <QProgressBar> 0031 #include <QToolButton> 0032 #include <QIcon> 0033 #include <QDBusConnection> 0034 #include <QStyle> 0035 #include <QApplication> 0036 #include <QScreen> 0037 #include <QMessageBox> 0038 #include <QTextEdit> 0039 #include <QMenu> 0040 #include <QDialog> 0041 #include <QDialogButtonBox> 0042 #include <QCryptographicHash> 0043 #include <QDebug> 0044 0045 #include <KLocalizedString> 0046 #include <KNotification> 0047 0048 //#define TEST_LOGOUT 0049 //#define TEST_REBOOT 0050 0051 //-------------------------------------------------------------------------------- 0052 0053 PkUpdateListItem::PkUpdateListItem(QWidget *parent, PackageKit::Transaction::Info info, const PkUpdates::PackageData &data) 0054 : QWidget(parent), package(data) 0055 { 0056 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0057 0058 QGridLayout *grid = new QGridLayout(this); 0059 grid->setContentsMargins(QMargins()); 0060 grid->setSpacing(0); 0061 0062 checkBox = new QCheckBox; 0063 checkBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0064 grid->addWidget(checkBox, 0, 0); 0065 0066 QString icon; 0067 switch ( info ) 0068 { 0069 case PackageKit::Transaction::InfoSecurity: icon = "update-high"; break; 0070 case PackageKit::Transaction::InfoImportant: icon = "update-medium"; break; 0071 case PackageKit::Transaction::InfoBugfix: icon = "update-low"; break; 0072 default: ; 0073 } 0074 QLabel *iconLabel = new QLabel; 0075 iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0076 iconLabel->setPixmap(QIcon::fromTheme(icon).pixmap(16)); 0077 grid->addWidget(iconLabel, 0, 1); 0078 0079 checkBox->setChecked(!icon.isEmpty()); // auto-check the important ones 0080 connect(checkBox, &QCheckBox::toggled, this, &PkUpdateListItem::toggled); 0081 0082 label = new QToolButton; 0083 label->setAutoRaise(true); 0084 label->setText(package.summary + " [" + 0085 PackageKit::Daemon::packageName(package.id) + ", " + 0086 PackageKit::Daemon::packageVersion(package.id) + ']'); 0087 grid->addWidget(label, 0, 2, Qt::AlignLeft); 0088 0089 detailsLabel = new QLabel; 0090 detailsLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); 0091 detailsLabel->setIndent(30); 0092 detailsLabel->setAlignment(Qt::AlignLeft); 0093 grid->addWidget(detailsLabel, 1, 2); 0094 detailsLabel->hide(); 0095 0096 connect(label, &QToolButton::clicked, this, &PkUpdateListItem::getUpdateDetails); 0097 0098 { 0099 QHBoxLayout *progressHbox = new QHBoxLayout; 0100 progressHbox->setSpacing(10); 0101 progress = new QProgressBar; 0102 progress->setFixedWidth(300); 0103 progressHbox->addWidget(progress); 0104 0105 errorLabel = new QLabel; 0106 errorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); 0107 errorLabel->hide(); 0108 progressHbox->addWidget(errorLabel); 0109 0110 packageLabel = new QLabel; 0111 packageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); 0112 packageLabel->hide(); 0113 progressHbox->addWidget(packageLabel); 0114 0115 grid->addLayout(progressHbox, 2, 2, Qt::AlignLeft); 0116 showProgress(false); 0117 } 0118 } 0119 0120 //-------------------------------------------------------------------------------- 0121 0122 void PkUpdateListItem::showProgress(bool yes) 0123 { 0124 progress->setValue(0); 0125 progress->setVisible(yes); 0126 } 0127 0128 //-------------------------------------------------------------------------------- 0129 0130 void PkUpdateListItem::getUpdateDetails() 0131 { 0132 if ( !detailsLabel->isHidden() ) 0133 { 0134 detailsLabel->hide(); 0135 return; 0136 } 0137 0138 //qDebug() << "getUpdateDetails" << package.id << PackageKit::Daemon::packageName(package.id); 0139 PackageKit::Transaction *transaction = PackageKit::Daemon::getUpdateDetail(package.id); 0140 detailsLabel->setText(i18n("Getting details ...")); 0141 detailsLabel->show(); 0142 0143 connect(transaction, &PackageKit::Transaction::updateDetail, this, &PkUpdateListItem::updateDetail); 0144 0145 connect(transaction, &PackageKit::Transaction::errorCode, this, 0146 [this](PackageKit::Transaction::Error error, const QString &details) 0147 { 0148 Q_UNUSED(error) 0149 detailsLabel->setText(details); 0150 }); 0151 } 0152 0153 //-------------------------------------------------------------------------------- 0154 0155 void PkUpdateListItem::updateDetail(const QString &packageID, 0156 const QStringList &updates, 0157 const QStringList &obsoletes, 0158 const QStringList &vendorUrls, 0159 const QStringList &bugzillaUrls, 0160 const QStringList &cveUrls, 0161 PackageKit::Transaction::Restart restart, 0162 const QString &updateText, 0163 const QString &changelog, 0164 PackageKit::Transaction::UpdateState state, 0165 const QDateTime &issued, 0166 const QDateTime &updated) 0167 { 0168 Q_UNUSED(packageID) 0169 Q_UNUSED(updates) 0170 Q_UNUSED(obsoletes) 0171 Q_UNUSED(vendorUrls) 0172 Q_UNUSED(bugzillaUrls) 0173 Q_UNUSED(cveUrls) 0174 Q_UNUSED(changelog) 0175 Q_UNUSED(state) 0176 Q_UNUSED(issued) 0177 Q_UNUSED(updated) 0178 0179 QString text; 0180 0181 if ( restart == PackageKit::Transaction::RestartSession ) 0182 text = i18n("<b>Session restart required</b><br>"); 0183 else if ( restart == PackageKit::Transaction::RestartSystem ) 0184 text = i18n("<b>Reboot required</b><br>"); 0185 0186 text += updateText.trimmed(); 0187 text.replace("\n", "<br>"); 0188 0189 detailsLabel->setText(text); 0190 } 0191 0192 //-------------------------------------------------------------------------------- 0193 //-------------------------------------------------------------------------------- 0194 //-------------------------------------------------------------------------------- 0195 0196 PkUpdateList::PkUpdateList(QWidget *parent) 0197 : QWidget(parent), restart(PackageKit::Transaction::RestartNone) 0198 { 0199 setWindowFlags(windowFlags() | Qt::Tool); 0200 setWindowTitle(i18n("Software Updates")); 0201 0202 vbox = new QVBoxLayout(this); 0203 QMargins margins = vbox->contentsMargins(); 0204 vbox->setContentsMargins(QMargins(0, -1, 0, 0)); 0205 0206 // action buttons 0207 QHBoxLayout *hbox = new QHBoxLayout; 0208 margins.setTop(0); 0209 margins.setBottom(0); 0210 hbox->setContentsMargins(margins); 0211 0212 checkAllBox = new QCheckBox(i18n("All")); 0213 connect(checkAllBox, &QCheckBox::toggled, this, &PkUpdateList::checkAll); 0214 0215 filterEdit = new QLineEdit; 0216 filterEdit->setPlaceholderText(i18n("Filter")); 0217 filterEdit->setClearButtonEnabled(true); 0218 connect(filterEdit, &QLineEdit::textEdited, this, &PkUpdateList::filterChanged); 0219 0220 // create "busy" indicator 0221 progressBar = new QProgressBar; 0222 0223 installButton = new QToolButton; 0224 installButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0225 installButton->setText(i18n("Install")); 0226 installButton->setEnabled(false); 0227 connect(installButton, &QToolButton::clicked, [this]() { afterInstall = AfterInstall::Nothing; install(); }); 0228 0229 // more install options 0230 installMenu = new QMenu; 0231 installMenu->addAction(i18n("Install, then Sleep"), this, [this]() { afterInstall = AfterInstall::Sleep; install(); }); 0232 installMenu->addAction(i18n("Install, then Shutdown"), this, [this]() { afterInstall = AfterInstall::Shutdown; install(); }); 0233 installButton->setMenu(installMenu); 0234 0235 refreshButton = new QToolButton; 0236 refreshButton->setText(i18n("Refresh")); 0237 connect(refreshButton, &QToolButton::clicked, this, [this]() { setPackages(PkUpdates::PackageList()); emit refreshRequested(); }); 0238 0239 hbox->addWidget(checkAllBox); 0240 hbox->addWidget(filterEdit); 0241 hbox->addWidget(progressBar); 0242 hbox->addWidget(installButton); 0243 hbox->addWidget(refreshButton); 0244 vbox->addLayout(hbox); 0245 0246 // list of items in the order: security, important, bugfix, others 0247 scrollArea = new QScrollArea; 0248 scrollArea->setWidgetResizable(true); 0249 0250 vbox->addWidget(scrollArea); 0251 0252 QWidget *w = new QWidget; 0253 vbox = new QVBoxLayout(w); 0254 itemsLayout = new QVBoxLayout; 0255 itemsLayout->setSpacing(0); 0256 vbox->addLayout(itemsLayout); 0257 vbox->addStretch(); 0258 0259 scrollArea->setWidget(w); 0260 } 0261 0262 //-------------------------------------------------------------------------------- 0263 0264 QSize PkUpdateList::sizeHint() const 0265 { 0266 QSize s; 0267 0268 if ( savedSize.isValid() ) 0269 s = savedSize; 0270 else 0271 { 0272 s = scrollArea->widget()->sizeHint() + QSize(2 * scrollArea->frameWidth(), 2 * scrollArea->frameWidth()); 0273 s.setWidth(s.width() + scrollArea->verticalScrollBar()->sizeHint().width()); 0274 s.setHeight(layout()->contentsMargins().top() + installButton->sizeHint().height() + 0275 ((layout()->spacing() == -1) ? style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) : layout()->spacing()) + 0276 s.height() + layout()->contentsMargins().bottom()); 0277 0278 s = s.expandedTo(QSize(500, 300)); 0279 } 0280 0281 // Qt doc: The size of top-level widgets are constrained to 2/3 of the desktop's height and width. 0282 QSize screen = DesktopWidget::availableSize(); 0283 s = s.boundedTo(screen * 2 / 3); 0284 0285 return s; 0286 } 0287 0288 //-------------------------------------------------------------------------------- 0289 0290 void PkUpdateList::hideEvent(QHideEvent *event) 0291 { 0292 Q_UNUSED(event) 0293 0294 if ( itemsLayout->count() ) 0295 savedSize = size(); 0296 else 0297 savedSize = QSize(); 0298 } 0299 0300 //-------------------------------------------------------------------------------- 0301 0302 void PkUpdateList::setRefreshProgress(int progress) 0303 { 0304 if ( progress >= 100 ) 0305 { 0306 filterEdit->show(); 0307 progressBar->hide(); 0308 refreshButton->setEnabled(true); 0309 } 0310 else if ( progress == 0 ) 0311 { 0312 // we don't know if there will be a value between 0..100 0313 // Until the first value arrives, use a busy indicator 0314 progressBar->setMinimum(0); 0315 progressBar->setMaximum(0); 0316 progressBar->setValue(0); 0317 progressBar->show(); 0318 filterEdit->hide(); 0319 refreshButton->setEnabled(false); 0320 } 0321 else 0322 { 0323 progressBar->setMinimum(0); 0324 progressBar->setMaximum(100); 0325 progressBar->setValue(progress); 0326 } 0327 } 0328 0329 //-------------------------------------------------------------------------------- 0330 0331 bool comparePackageData(const PkUpdates::PackageData &a, const PkUpdates::PackageData &b) 0332 { 0333 return a.summary.localeAwareCompare(b.summary) < 0; 0334 } 0335 0336 //-------------------------------------------------------------------------------- 0337 0338 void PkUpdateList::setPackages(const PkUpdates::PackageList &packages) 0339 { 0340 itemsLayout->parentWidget()->layout()->setEnabled(false); 0341 itemsLayout->setEnabled(false); 0342 0343 QLayoutItem *child; 0344 while ( (child = itemsLayout->takeAt(0)) ) 0345 { 0346 delete child->widget(); 0347 delete child; 0348 } 0349 0350 checkAllBox->setChecked(false); 0351 0352 QList<PkUpdates::PackageData> list = packages.values(PackageKit::Transaction::InfoSecurity); 0353 std::sort(list.begin(), list.end(), comparePackageData); 0354 for (const PkUpdates::PackageData &data : list) 0355 { 0356 auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoSecurity, data); 0357 connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); 0358 itemsLayout->addWidget(item); 0359 item->show(); 0360 } 0361 0362 list = packages.values(PackageKit::Transaction::InfoImportant); 0363 std::sort(list.begin(), list.end(), comparePackageData); 0364 for (const PkUpdates::PackageData &data : list) 0365 { 0366 auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoImportant, data); 0367 connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); 0368 itemsLayout->addWidget(item); 0369 item->show(); 0370 } 0371 0372 list = packages.values(PackageKit::Transaction::InfoBugfix); 0373 std::sort(list.begin(), list.end(), comparePackageData); 0374 for (const PkUpdates::PackageData &data : list) 0375 { 0376 auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoBugfix, data); 0377 connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); 0378 itemsLayout->addWidget(item); 0379 item->show(); 0380 } 0381 0382 // all the others 0383 list.clear(); 0384 for (PkUpdates::PackageList::const_iterator it = packages.constBegin(); it != packages.constEnd(); ++it) 0385 { 0386 if ( (it.key() != PackageKit::Transaction::InfoSecurity) && 0387 (it.key() != PackageKit::Transaction::InfoImportant) && 0388 (it.key() != PackageKit::Transaction::InfoBugfix) ) 0389 { 0390 list.append(it.value()); 0391 } 0392 } 0393 std::sort(list.begin(), list.end(), comparePackageData); 0394 for (const PkUpdates::PackageData &data : list) 0395 { 0396 auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoUnknown, data); 0397 connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); 0398 itemsLayout->addWidget(item); 0399 item->show(); 0400 } 0401 0402 itemsLayout->setEnabled(true); 0403 0404 filterChanged(filterEdit->text()); 0405 0406 countChecked(); 0407 } 0408 0409 //-------------------------------------------------------------------------------- 0410 0411 void PkUpdateList::checkAll(bool on) 0412 { 0413 for (int i = 0; i < itemsLayout->count(); i++) 0414 { 0415 PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget()); 0416 0417 if ( item && !item->isHidden() ) 0418 { 0419 item->checkBox->blockSignals(true); 0420 item->checkBox->setChecked(on); 0421 item->checkBox->blockSignals(false); 0422 } 0423 } 0424 countChecked(); 0425 } 0426 0427 //-------------------------------------------------------------------------------- 0428 0429 void PkUpdateList::countChecked() 0430 { 0431 int count = 0; 0432 0433 for (int i = 0; i < itemsLayout->count(); i++) 0434 { 0435 PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget()); 0436 0437 if ( item && !item->isHidden() && item->checkBox->isChecked() ) 0438 count++; 0439 } 0440 0441 if ( count ) 0442 { 0443 installButton->setText(i18np("Install %1 package", "Install %1 packages", count)); 0444 installButton->setEnabled(true); 0445 installButton->setMenu(installMenu); 0446 } 0447 else 0448 { 0449 installButton->setText(i18n("Install")); 0450 installButton->setEnabled(false); 0451 } 0452 installButton->setIcon(QIcon()); 0453 refreshButton->setEnabled(true); 0454 } 0455 0456 //-------------------------------------------------------------------------------- 0457 0458 void PkUpdateList::filterChanged(const QString &text) 0459 { 0460 itemsLayout->parentWidget()->layout()->setEnabled(false); 0461 0462 for (int i = 0; i < itemsLayout->count(); i++) 0463 { 0464 PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget()); 0465 0466 item->setVisible(text.isEmpty() || (item->label->text().indexOf(text, 0, Qt::CaseInsensitive) != -1)); 0467 } 0468 0469 itemsLayout->parentWidget()->layout()->setEnabled(true); 0470 countChecked(); 0471 } 0472 0473 //-------------------------------------------------------------------------------- 0474 0475 void PkUpdateList::install() 0476 { 0477 if ( !installQ.isEmpty() ) // installation in progress; cancel it 0478 { 0479 QPointer<PkUpdateListItem> currentItem = installQ.head(); 0480 0481 if ( transaction ) 0482 { 0483 QDBusPendingReply<> reply = transaction->cancel(); 0484 reply.waitForFinished(); 0485 if ( reply.isError() && currentItem ) 0486 { 0487 currentItem->errorLabel->setText(reply.error().message()); 0488 currentItem->errorLabel->show(); 0489 } 0490 } 0491 0492 for (int i = 0; i < itemsLayout->count(); i++) 0493 { 0494 PkUpdateListItem *item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget()); 0495 0496 if ( !transaction || (item != currentItem) ) 0497 { 0498 item->showProgress(false); 0499 item->errorLabel->hide(); 0500 } 0501 } 0502 0503 installQ.clear(); 0504 emit packageCountToInstall(0); 0505 0506 countChecked(); 0507 return; 0508 } 0509 0510 restart = PackageKit::Transaction::RestartNone; 0511 0512 for (int i = 0; i < itemsLayout->count(); i++) 0513 { 0514 QPointer<PkUpdateListItem> item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget()); 0515 0516 if ( item && !item->isHidden() && item->checkBox->isChecked() ) 0517 { 0518 item->showProgress(true); 0519 item->errorLabel->hide(); 0520 item->detailsLabel->hide(); 0521 0522 installQ.enqueue(item); 0523 } 0524 } 0525 0526 installOne(); 0527 } 0528 0529 //-------------------------------------------------------------------------------- 0530 0531 void PkUpdateList::enqueue(const QString &packageID) 0532 { 0533 // find and add item with packageID 0534 for (int i = 0; i < itemsLayout->count(); i++) 0535 { 0536 QPointer<PkUpdateListItem> item = qobject_cast<PkUpdateListItem *>(itemsLayout->itemAt(i)->widget()); 0537 0538 if ( item && (item->package.id == packageID) ) 0539 { 0540 item->showProgress(true); 0541 item->errorLabel->hide(); 0542 item->detailsLabel->hide(); 0543 0544 installQ.enqueue(item); 0545 break; 0546 } 0547 } 0548 0549 if ( !installQ.isEmpty() ) 0550 installOne(); 0551 } 0552 0553 //-------------------------------------------------------------------------------- 0554 0555 void PkUpdateList::installOne() 0556 { 0557 emit packageCountToInstall(installQ.count()); 0558 0559 if ( installQ.isEmpty() ) // installation finished 0560 { 0561 if ( restart != PackageKit::Transaction::RestartNone ) 0562 { 0563 QString text; 0564 0565 if ( ((restart == PackageKit::Transaction::RestartSystem) || 0566 (restart == PackageKit::Transaction::RestartSecuritySystem)) && 0567 (afterInstall != AfterInstall::Shutdown) ) // when we shutdown, no need to tell the user to restart 0568 { 0569 KNotification *notif = new KNotification("restart needed", KNotification::Persistent); 0570 notif->setWidget(parentWidget()); 0571 0572 notif->setTitle(i18n("System Reboot Required")); 0573 notif->setText(i18n("One of the installed packages requires a system reboot")); 0574 notif->setActions(QStringList() << i18n("Reboot System")); 0575 0576 connect(notif, &KNotification::action1Activated, this, 0577 []() 0578 { 0579 QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", 0580 "org.kde.KSMServerInterface", "logout"); 0581 msg << 0/*no confirm*/ << 1/*reboot*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h 0582 0583 QDBusConnection::sessionBus().send(msg); 0584 }); 0585 0586 notif->sendEvent(); 0587 } 0588 else if ( (restart == PackageKit::Transaction::RestartSession) || 0589 (restart == PackageKit::Transaction::RestartSecuritySession) ) 0590 { 0591 KNotification *notif = new KNotification("restart needed", KNotification::Persistent); 0592 notif->setWidget(parentWidget()); 0593 0594 notif->setTitle(i18n("Session Restart Required")); 0595 notif->setText(i18n("One of the installed packages requires you to logout")); 0596 notif->setActions(QStringList() << i18n("Logout")); 0597 0598 connect(notif, &KNotification::action1Activated, this, 0599 []() 0600 { 0601 QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", 0602 "org.kde.KSMServerInterface", "logout"); 0603 msg << 0/*no confirm*/ << 0/*logout*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h 0604 0605 QDBusConnection::sessionBus().send(msg); 0606 }); 0607 0608 notif->sendEvent(); 0609 } 0610 } 0611 0612 countChecked(); 0613 0614 if ( eulaDialogs.isEmpty() ) 0615 { 0616 if ( afterInstall == AfterInstall::Sleep ) 0617 { 0618 QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.PowerManagement", 0619 "/org/freedesktop/PowerManagement", 0620 "org.freedesktop.PowerManagement", "Suspend"); 0621 0622 QDBusConnection::sessionBus().send(msg); 0623 } 0624 else if ( afterInstall == AfterInstall::Shutdown ) 0625 { 0626 QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", 0627 "org.kde.KSMServerInterface", "logout"); 0628 msg << 0/*no confirm*/ << 2/*halt*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h 0629 0630 QDBusConnection::sessionBus().send(msg); 0631 } 0632 } 0633 0634 return; 0635 } 0636 0637 QPointer<PkUpdateListItem> item = installQ.head(); 0638 0639 if ( !item ) 0640 return; 0641 0642 installButton->setText(i18n("Cancel Installation")); 0643 installButton->setMenu(nullptr); 0644 installButton->setIcon(QIcon::fromTheme("process-stop")); 0645 refreshButton->setEnabled(false); 0646 0647 PackageKit::Transaction::TransactionFlag flag = PackageKit::Transaction::TransactionFlagOnlyTrusted; 0648 #ifdef TEST_LOGOUT 0649 flag |= PackageKit::Transaction::TransactionFlagSimulate; 0650 restart = PackageKit::Transaction::RestartSession; 0651 #elif defined TEST_REBOOT 0652 flag |= PackageKit::Transaction::TransactionFlagSimulate; 0653 restart = PackageKit::Transaction::RestartSystem; 0654 #endif 0655 transaction = PackageKit::Daemon::updatePackage(item->package.id, flag); 0656 packageNoLongerAvailable = false; 0657 //qDebug() << "installing" << item->package.id; 0658 0659 connect(transaction.data(), &PackageKit::Transaction::allowCancelChanged, this, 0660 [this]() 0661 { 0662 installButton->setEnabled(transaction->allowCancel()); 0663 }); 0664 0665 connect(transaction.data(), &PackageKit::Transaction::statusChanged, this, 0666 [item, this]() 0667 { 0668 if ( !item ) // already deleted 0669 return; 0670 0671 //qDebug() << "status" << QMetaEnum::fromType<PackageKit::Transaction::Status>().valueToKey(transaction->status()); 0672 QString text; 0673 switch ( transaction->status() ) 0674 { 0675 case PackageKit::Transaction::StatusWait: text = i18n("Waiting"); break; 0676 case PackageKit::Transaction::StatusWaitingForAuth: text = i18n("Waiting for authentication"); break; 0677 case PackageKit::Transaction::StatusDepResolve: text = i18n("Resolving dependencies"); break; 0678 case PackageKit::Transaction::StatusUpdate: text = i18n("Updating"); break; 0679 case PackageKit::Transaction::StatusInstall: text = i18n("Installing"); break; 0680 case PackageKit::Transaction::StatusDownload: text = i18n("Downloading"); break; 0681 case PackageKit::Transaction::StatusCancel: text = i18n("Canceling"); break; 0682 case PackageKit::Transaction::StatusFinished: return; // don't hide error label 0683 default: return; 0684 } 0685 0686 if ( text.isEmpty() ) 0687 item->errorLabel->hide(); 0688 else 0689 { 0690 item->errorLabel->setText(text); 0691 item->errorLabel->show(); 0692 } 0693 }); 0694 0695 connect(transaction.data(), &PackageKit::Transaction::itemProgress, this, 0696 [item](const QString &itemID, PackageKit::Transaction::Status status, uint percentage) 0697 { 0698 Q_UNUSED(status) 0699 0700 if ( !item ) // already deleted 0701 return; 0702 0703 item->packageLabel->setText(PackageKit::Daemon::packageName(itemID)); 0704 item->packageLabel->show(); 0705 0706 if ( percentage <= 100 ) // 101 .. unknown 0707 item->progress->setValue(percentage); 0708 }); 0709 0710 connect(transaction.data(), &PackageKit::Transaction::requireRestart, this, 0711 [this](PackageKit::Transaction::Restart type, const QString &/*packageID*/) 0712 { 0713 // keep most important restart type: System, Session 0714 if ( (type == PackageKit::Transaction::RestartSystem) || 0715 (type == PackageKit::Transaction::RestartSecuritySystem) || 0716 0717 (((type == PackageKit::Transaction::RestartSession) || 0718 (type == PackageKit::Transaction::RestartSecuritySession)) && 0719 (restart != PackageKit::Transaction::RestartSystem) && 0720 (restart != PackageKit::Transaction::RestartSecuritySystem)) ) 0721 restart = type; 0722 }); 0723 0724 connect(transaction.data(), &PackageKit::Transaction::eulaRequired, this, &PkUpdateList::askEULA); 0725 0726 connect(transaction.data(), &PackageKit::Transaction::repoSignatureRequired, this, 0727 [this](const QString &packageID, 0728 const QString &repoName, 0729 const QString &keyUrl, 0730 const QString &keyUserid, 0731 const QString &keyId, 0732 const QString &keyFingerprint, 0733 const QString &keyTimestamp, 0734 PackageKit::Transaction::SigType type) 0735 { 0736 if ( QMessageBox::question(this, i18n("Validate Signature"), 0737 QString("%1\n%2\n%3\n%4\n%5\n%6\n%7") 0738 .arg(packageID) 0739 .arg(repoName) 0740 .arg(keyUrl) 0741 .arg(keyUserid) 0742 .arg(keyId) 0743 .arg(keyFingerprint) 0744 .arg(keyTimestamp)) == QMessageBox::Yes ) 0745 { 0746 PackageKit::Daemon::installSignature(type, keyId, packageID); 0747 } 0748 }); 0749 0750 connect(transaction.data(), &PackageKit::Transaction::errorCode, this, 0751 [item, this](PackageKit::Transaction::Error error, const QString &details) 0752 { 0753 if ( !item ) 0754 return; 0755 0756 item->showProgress(false); 0757 item->errorLabel->setText(details); 0758 item->errorLabel->show(); 0759 //qDebug() << "errorCode" << details << QMetaEnum::fromType<PackageKit::Transaction::Error>().valueToKey(error); 0760 0761 if ( (error == PackageKit::Transaction::ErrorDepResolutionFailed) && 0762 (transaction->status() == PackageKit::Transaction::StatusSetup) ) 0763 { 0764 // when a package was already installed due to another dependency, then it's no more an update candidate. 0765 // Still the finished() signal will arrive which will do cleanup (delete item, dequeue, etc.). 0766 packageNoLongerAvailable = true; 0767 } 0768 }); 0769 0770 connect(transaction.data(), &PackageKit::Transaction::finished, this, 0771 [item, this](PackageKit::Transaction::Exit status, uint runtime) 0772 { 0773 Q_UNUSED(runtime) 0774 0775 if ( !item ) 0776 return; 0777 0778 if ( (status == PackageKit::Transaction::ExitSuccess) || packageNoLongerAvailable ) 0779 { 0780 item->hide(); 0781 item->deleteLater(); 0782 emit packageInstalled(item->package.id); 0783 } 0784 else 0785 { 0786 item->showProgress(false); 0787 item->detailsLabel->setText(i18n("Update failed")); 0788 item->detailsLabel->show(); 0789 } 0790 0791 if ( !installQ.isEmpty() ) // might have been cancelled, q cleared 0792 installQ.dequeue(); 0793 0794 installOne(); 0795 }); 0796 } 0797 0798 //-------------------------------------------------------------------------------- 0799 0800 void PkUpdateList::askEULA(const QString &eulaID, const QString &packageID, 0801 const QString &vendor, const QString &licenseAgreement) 0802 { 0803 // I get several different eulaIDs but each having the same licenseAgreement text. Don't bother the user 0804 // showing multiple times the same licenseAgreement 0805 QByteArray hash = QCryptographicHash::hash(licenseAgreement.trimmed().toUtf8(), QCryptographicHash::Sha1); 0806 0807 //qDebug() << "askEULA" << eulaID << packageID << hash.toBase64(); 0808 0809 // we can get multiple signals for the same eulaID 0810 // when installing multiple packages using the same EULA 0811 // or we get different eulaIDs with the same text 0812 if ( eulaDialogs.contains(hash) ) 0813 { 0814 bool found = false; 0815 for (const EulaPackage &ep : eulaDialogs.values(hash)) 0816 { 0817 if ( ep.eulaID == eulaID ) 0818 { 0819 found = true; 0820 break; 0821 } 0822 } 0823 0824 if ( !found ) 0825 eulaDialogs.insert(hash, EulaPackage(eulaID, packageID)); 0826 0827 return; 0828 } 0829 0830 if ( acceptedEula.contains(hash) ) 0831 { 0832 //qDebug() << "already accepted - enqueue again"; 0833 PackageKit::Daemon::acceptEula(eulaID); 0834 enqueue(packageID); 0835 return; 0836 } 0837 0838 eulaDialogs.insert(hash, EulaPackage(eulaID, packageID)); 0839 0840 QDialog *dialog = new QDialog; 0841 dialog->setModal(false); 0842 dialog->setWindowTitle(i18n("Accept EULA")); 0843 0844 QVBoxLayout *vbox = new QVBoxLayout(dialog); 0845 QHBoxLayout *hbox = new QHBoxLayout; 0846 0847 QLabel *iconLabel = new QLabel; 0848 iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0849 iconLabel->setAlignment(Qt::AlignTop); 0850 QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, this); 0851 int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, nullptr, this); 0852 iconLabel->setPixmap(icon.pixmap(windowHandle(), QSize(iconSize, iconSize))); 0853 0854 QLabel *label = new QLabel(i18n("<html>Do you accept the following license agreement " 0855 "for package<br>%1<br>from vendor<br>%2 ?</html>").arg(packageID, vendor)); 0856 label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); 0857 label->setTextInteractionFlags(Qt::TextBrowserInteraction); 0858 0859 hbox->addWidget(iconLabel); 0860 hbox->addWidget(label); 0861 0862 vbox->addLayout(hbox); 0863 0864 QTextEdit *textEdit = new QTextEdit; 0865 textEdit->setPlainText(licenseAgreement); 0866 textEdit->setReadOnly(true); 0867 vbox->addWidget(textEdit); 0868 0869 QDialogButtonBox *buttonBox = new QDialogButtonBox(); 0870 buttonBox->setCenterButtons(style()->styleHint(QStyle::SH_MessageBox_CenterButtons, nullptr, this)); 0871 buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); 0872 buttonBox->button(QDialogButtonBox::Yes)->setDefault(true); 0873 0874 connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); 0875 connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); 0876 vbox->addWidget(buttonBox); 0877 0878 connect(dialog, &QDialog::finished, this, 0879 [this, dialog, hash](int result) 0880 { 0881 if ( result == QDialog::Accepted ) 0882 { 0883 for (const EulaPackage &ep : eulaDialogs.values(hash)) 0884 { 0885 PackageKit::Daemon::acceptEula(ep.eulaID); 0886 enqueue(ep.packageID); 0887 } 0888 0889 acceptedEula.insert(hash); 0890 } 0891 0892 eulaDialogs.remove(hash); 0893 dialog->deleteLater(); 0894 }); 0895 0896 // The dialog will be shown non modal and used asynchronously to the installation. 0897 // PackageKit Daemon will already finish this package with an error of 0898 // something like "you need to agree/decline the license" 0899 // So when the user decides what to do, he must afterwards start the installation 0900 // for this package again 0901 dialog->show(); 0902 } 0903 0904 //--------------------------------------------------------------------------------