From 1ccbb8b1a0db06758d3c0809e6b932b594f5eb8f Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Wed, 10 Jan 2024 21:01:15 -0600 Subject: [PATCH] A bunch of improvements --- CMakeLists.txt | 14 ++++++++++++++ README.md | 10 +++++++++- aptmanager.cpp | 2 +- ipcfilewatcher.cpp | 38 ++++++++++++++++++++++++++++++++++++++ ipcfilewatcher.h | 22 ++++++++++++++++++++++ lubuntu-update.desktop | 11 +++++++++++ main.cpp | 39 +++++++++++++++++++++++++++++++++++++++ mainwindow.cpp | 22 +++++++++++++++++++++- mainwindow.h | 1 + orchestrator.cpp | 13 ++++++++++--- orchestrator.h | 4 +++- 11 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 ipcfilewatcher.cpp create mode 100644 ipcfilewatcher.h create mode 100644 lubuntu-update.desktop diff --git a/CMakeLists.txt b/CMakeLists.txt index b9fd8a0..cb542b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ set(PROJECT_SOURCES conffilehandlerdialog.h conffilehandlerdialog.cpp conffilehandlerdialog.ui + ipcfilewatcher.h + ipcfilewatcher.cpp ${TS_FILES} ) @@ -60,6 +62,18 @@ install(TARGETS lubuntu-update RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) +# Yes, we are hardcoding a path here. Yes, it's ugly. However, the program +# will attempt to run this script as root, and I do NOT want to make it try to +# "find" the script and execute the first thing it finds that has the right +# name, that sounds like a security breach waiting to happen. It will only +# execute lubuntu-update-backend from /usr/libexec, and if something malicious +# has gotten into /usr/libexec you're compromised anyway, so not much to worry +# about. Therefore we always, *always* install this script into /usr/libexec/, +# no matter where the rest of the program goes. +install(FILES lubuntu-update-backend DESTINATION /usr/libexec/) + +install(FILES lubuntu-update.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications/) + if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(lubuntu-update) endif() diff --git a/README.md b/README.md index 6505502..f3b8ead 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,16 @@ cmake .. make -j$(nproc) ``` -To use, copy the lubuntu-update-backend script to /usr/bin/lubuntu-update-backend, then compile and run the updater. It is highly recommended that you **do not** install the updater all the way into your system with `sudo make install` or similar. +To use, copy the lubuntu-update-backend script to /usr/libexec/lubuntu-update-backend, then compile and run the updater. It is highly recommended that you **do not** install the updater all the way into your system with `sudo make install` or similar. It is highly recommended that you use a Lubuntu virtual machine for testing and development. Use the latest development release if at all possible, unless you know you need to test an earlier release. Qt Creator is recommended for editing the code. It is present in Ubuntu's official repos and can be installed using `sudo apt install qtcreator`. + +## Missing features + +* The Details button is hidden and does nothing. Eventually it should display a list of packages, the old version of them, the new version of them, and a link to their Launchpad page. +* There's no support for release upgrading. This is currently unnecessary as this updater is only going to be shipped in Noble and later, but it will become a big deal in the (potentially near) future. +* There's no support for doing an `apt update` for checking for recent updates. This seems rather important *now*. +* Most of the internal strings aren't translatable... +* ...and the bit of translation support there is, is totally untested and quite possibly not functioning properly. Borrow from what we did with lubuntu-installer-prompt to fix this. diff --git a/aptmanager.cpp b/aptmanager.cpp index 4768702..505dd98 100644 --- a/aptmanager.cpp +++ b/aptmanager.cpp @@ -28,7 +28,7 @@ void AptManager::applyFullUpgrade() aptProcess = new QProcess(); aptProcess->setProgram("/usr/bin/lxqt-sudo"); // Note that the lubuntu-update-backend script sets LC_ALL=C in it already, so we don't need to add that here. - aptProcess->setArguments(QStringList() << "/usr/bin/lubuntu-update-backend" << "doupdate"); + aptProcess->setArguments(QStringList() << "/usr/libexec/lubuntu-update-backend" << "doupdate"); aptProcess->setProcessChannelMode(QProcess::MergedChannels); QObject::connect(aptProcess, &QProcess::readyRead, this, &AptManager::handleProcessBuffer); QObject::connect(aptProcess, QOverload::of(&QProcess::finished), this, &AptManager::handleProcessBuffer); diff --git a/ipcfilewatcher.cpp b/ipcfilewatcher.cpp new file mode 100644 index 0000000..c79d6f3 --- /dev/null +++ b/ipcfilewatcher.cpp @@ -0,0 +1,38 @@ +#include "ipcfilewatcher.h" + +#include +#include +#include +#include + +#include + +IPCFileWatcher::IPCFileWatcher(QObject *parent) + : QObject{parent} +{ + QDir targetDir("/dev/shm/lubuntu-update"); + bool couldRemove = targetDir.removeRecursively(); + if (!couldRemove) { + qCritical() << "Could not clear IPC directory. Ensure that /dev/shm and /dev/shm/lubuntu-update are world-readable and world-writable."; + initFailed = true; + return; + } + targetDir.mkdir("/dev/shm/lubuntu-update"); + QFileSystemWatcher *watcher = new QFileSystemWatcher(QStringList() << "/dev/shm/lubuntu-update"); + connect(watcher, &QFileSystemWatcher::directoryChanged, this, &IPCFileWatcher::checkForShowWindowFile); + initFailed = false; +} + +bool IPCFileWatcher::didInitFail() +{ + return initFailed; +} + +void IPCFileWatcher::checkForShowWindowFile() +{ + QFile flagFile("/dev/shm/lubuntu-update/lubuntu-update-show-win"); + if (flagFile.exists()) { + flagFile.remove(); + emit showWindowFlagDetected(); + } +} diff --git a/ipcfilewatcher.h b/ipcfilewatcher.h new file mode 100644 index 0000000..d4080b7 --- /dev/null +++ b/ipcfilewatcher.h @@ -0,0 +1,22 @@ +#ifndef IPCFILEWATCHER_H +#define IPCFILEWATCHER_H + +#include + +class IPCFileWatcher : public QObject +{ + Q_OBJECT +public: + explicit IPCFileWatcher(QObject *parent = nullptr); + bool didInitFail(); + +signals: + void showWindowFlagDetected(); + +private: + bool initFailed; + + void checkForShowWindowFile(); +}; + +#endif // IPCFILEWATCHER_H diff --git a/lubuntu-update.desktop b/lubuntu-update.desktop new file mode 100644 index 0000000..80c02b3 --- /dev/null +++ b/lubuntu-update.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Exec=/usr/bin/lubuntu-update +Name=Lubuntu Update +GenericName=Lubuntu Update +Comment=View available updates and optionally install them +Icon=system-software-update +Type=Application +Version=0.1 +Categories=System;Settings; +Keywords=upgrade;update +Terminal=false diff --git a/main.cpp b/main.cpp index 5d4a342..0d7ab0e 100644 --- a/main.cpp +++ b/main.cpp @@ -1,3 +1,4 @@ +#include "ipcfilewatcher.h" #include "orchestrator.h" #include "mainwindow.h" #include "conffilehandlerdialog.h" @@ -5,6 +6,7 @@ #include #include #include +#include #include int main(int argc, char *argv[]) @@ -20,9 +22,44 @@ int main(int argc, char *argv[]) break; } } + + /* + * If Lubuntu Update is already running, create + * /dev/shm/lubuntu-update/lubuntu-update-show-win and exit. This will + * trigger the existing process to pop up a window. + */ + + QProcess procDetector; + procDetector.setProgram("/usr/bin/bash"); + procDetector.setArguments(QStringList() << "-c" << "ps axo comm | grep lubuntu-update"); + procDetector.start(); + procDetector.waitForFinished(); + QString procDetectResult = procDetector.readAllStandardOutput(); + procDetectResult = procDetectResult.trimmed(); + QStringList procDetectResultList = procDetectResult.split('\n'); + if (procDetectResultList.count() > 1) { + QFile flagFile("/dev/shm/lubuntu-update/lubuntu-update-show-win"); + flagFile.open(QFile::WriteOnly); + flagFile.close(); + return 0; + } + // Don't want the updater to stop just because the user closed it :P a.setQuitOnLastWindowClosed(false); + /* + * IPCFileWatcher just watches the /dev/shm/lubuntu-update folder for the + * creation of a lubuntu-update-show-win file. If it detects it, it + * immediately deletes it and emits a signal. This is then used later to + * cause the updater window to pop up. + */ + + IPCFileWatcher *p = new IPCFileWatcher(); + + if (p->didInitFail()) { + return 1; + } + /* * As this is a background process, we don't pop up any window upon * startup. An Orchestrator object periodically checks to see if new @@ -37,6 +74,8 @@ int main(int argc, char *argv[]) Orchestrator *o = new Orchestrator(); + QObject::connect(p, &IPCFileWatcher::showWindowFlagDetected, o, &Orchestrator::displayUpdater); + /* * This is an artifact from testing the conffile handler window. You can * uncomment this and rebuild lubuntu-update in order to test the conffile diff --git a/mainwindow.cpp b/mainwindow.cpp index 506fbeb..b4a27b0 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -17,6 +17,9 @@ MainWindow::MainWindow(QWidget *parent) ui->progressBar->setVisible(false); ui->logView->setVisible(false); + // FIXME: Implement the Details screen and attach this button to it rather than disabling it. + ui->detailsButton->setVisible(false); + connect(ui->installButton, &QPushButton::clicked, this, &MainWindow::onInstallButtonClicked); connect(ui->closeButton, &QPushButton::clicked, this, &MainWindow::onCloseButtonClicked); connect(aptManager, &AptManager::updateComplete, this, &MainWindow::onUpdateCompleted); @@ -36,11 +39,15 @@ void MainWindow::setUpdateInfo(QList updateInfo) // The progress bar and log view are shown after the user chooses to begin installing updates ui->progressBar->setVisible(false); ui->logView->setVisible(false); - ui->installButton->setEnabled(true); ui->detailsButton->setEnabled(true); ui->closeButton->setEnabled(true); + ui->installButton->setEnabled(false); // Correct, it starts out false, we turn it to true if there are any updates. for (int i = 0;i < 4;i++) { + if (updateInfo[i].count() > 0) { + ui->installButton->setEnabled(true); + } + QTreeWidgetItem *installItem; switch (i) { case 0: @@ -69,6 +76,18 @@ void MainWindow::setUpdateInfo(QList updateInfo) QString::number(updateInfo[4].count()))); } +bool MainWindow::isLockedOpen() +{ + /* + * If the Close button is disabled, we do NOT want the window to close + * under virtually any circumstances. This allows the Orchestrator to + * determine whether or not it is save to close and re-open the window + * from the outside. + */ + + return !ui->closeButton->isEnabled(); +} + void MainWindow::closeEvent(QCloseEvent *event) { /* @@ -99,6 +118,7 @@ void MainWindow::onCloseButtonClicked() void MainWindow::onUpdateCompleted() { ui->closeButton->setEnabled(true); + ui->installButton->setEnabled(false); ui->progressBar->setVisible(false); ui->statLabel->setText("Update installation complete."); emit updatesInstalled(); // this tells the orchestrator to hide the tray icon diff --git a/mainwindow.h b/mainwindow.h index f2ef2b7..dc16cab 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -21,6 +21,7 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); void setUpdateInfo(QList updateInfo); + bool isLockedOpen(); signals: void updatesInstalled(); diff --git a/orchestrator.cpp b/orchestrator.cpp index c2a651d..36e4085 100644 --- a/orchestrator.cpp +++ b/orchestrator.cpp @@ -13,7 +13,7 @@ Orchestrator::Orchestrator(QObject *parent) trayIcon = new QSystemTrayIcon(); // this is shown to the user to offer updates connect(checkTimer, &QTimer::timeout, this, &Orchestrator::checkForUpdates); - connect(trayIcon, &QSystemTrayIcon::activated, this, [this](){this->displayUpdater(updateInfo);}); + connect(trayIcon, &QSystemTrayIcon::activated, this, &Orchestrator::displayUpdater); connect(&updaterWindow, &MainWindow::updatesInstalled, this, &Orchestrator::handleUpdatesInstalled); checkTimer->start(21600000); // check four times a day, at least one of those times unattended-upgrades should have refreshed the apt database @@ -39,9 +39,12 @@ void Orchestrator::checkForUpdates() } } -void Orchestrator::displayUpdater(QList updateInfo) +void Orchestrator::displayUpdater() { - if (!updaterWindow.isVisible()) { + if (!updaterWindow.isLockedOpen()) { + if (!updaterWindow.isVisible()) { + updaterWindow.hide(); + } updaterWindow.setUpdateInfo(updateInfo); updaterWindow.show(); } @@ -49,5 +52,9 @@ void Orchestrator::displayUpdater(QList updateInfo) void Orchestrator::handleUpdatesInstalled() { + // We can't clear the updateInfo list directly as MainWindow::setUpdateInfo requires that it contains five inner lists (even if those lists are all empty). + for (int i = 0;i < 5;i++) { + updateInfo[i].clear(); + } trayIcon->hide(); } diff --git a/orchestrator.h b/orchestrator.h index 64864f1..0e5cd45 100644 --- a/orchestrator.h +++ b/orchestrator.h @@ -15,9 +15,11 @@ class Orchestrator : public QObject public: explicit Orchestrator(QObject *parent = nullptr); +public slots: + void displayUpdater(); + private slots: void checkForUpdates(); - void displayUpdater(QList updateInfo); void handleUpdatesInstalled(); private: