diff --git a/src/appimageservices-interface/org.appimage.Services.Inspector.xml b/src/appimageservices-interface/org.appimage.Services.Inspector.xml index ee9bf4a2c4ef12c97a980443fbc88bf4b69596f9..93b4f937c01672fe1a8a33dd5a921bd605ca232f 100644 --- a/src/appimageservices-interface/org.appimage.Services.Inspector.xml +++ b/src/appimageservices-interface/org.appimage.Services.Inspector.xml @@ -5,6 +5,11 @@ + + + + + diff --git a/src/bin/CMakeLists.txt b/src/bin/CMakeLists.txt index e2680510be15ff152af27abe7aee8daca5e3d47a..305f0cc9a018602f7f1247d9528ca97ac83882b6 100644 --- a/src/bin/CMakeLists.txt +++ b/src/bin/CMakeLists.txt @@ -2,9 +2,11 @@ add_executable( plasma-appimage-integration main.cpp UpdateJob.cpp + RegisterJob.cpp RemoveJob.cpp InstallJob.cpp UninstallJob.cpp + TargetDataLoader.cpp ) target_link_libraries(plasma-appimage-integration appimageservices-interfaces KF5::KIOWidgets KF5::I18n KF5::Notifications) diff --git a/src/bin/InstallJob.cpp b/src/bin/InstallJob.cpp index 171c73c6ca4bfb5656d148cf5c2bb516c061e5d3..6bee3e7e59d866e125d31163dff03ab254bb51e3 100644 --- a/src/bin/InstallJob.cpp +++ b/src/bin/InstallJob.cpp @@ -15,8 +15,8 @@ void InstallJob::start() { qDebug() << "calling pkexec appimage-services install " << target; connect(&process, SIGNAL(finished(int)), this, SLOT(onProcessFinished(int))); - description(this, i18n("Installing Application"), - qMakePair(i18nc("The AppImage being installed", "Source"), target)); + description(this, i18n("Installing application"), + qMakePair(i18nc("The AppImage being installed", "Application"), target)); process.start(); } diff --git a/src/bin/RegisterJob.cpp b/src/bin/RegisterJob.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3e05fe35a47115ae205a6c467607d343bd28bf6d --- /dev/null +++ b/src/bin/RegisterJob.cpp @@ -0,0 +1,37 @@ +// libraries +#include + +// local +#include "RegisterJob.h" +#include "LauncherInterface.h" + +RegisterJob::RegisterJob(const QString& target, QObject* parent) + : KJob(parent), target(target), + launcherInterface(new OrgAppimageServices1LauncherInterface("org.appimage.Services1.Launcher", + "/org/appimage/Services1/Launcher", + QDBusConnection::sessionBus(), this)) {} + +void RegisterJob::start() { + description(this, i18n("Creating launcher entry"), + qMakePair(i18nc("Target AppImage", "Application"), target)); + + auto reply = launcherInterface->registerApp(target); + if (reply.isError()) { + setError(-1); + setErrorText(reply.error().message()); + } + + auto* watcher = new QDBusPendingCallWatcher(reply, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &RegisterJob::callFinishedSlot); +} + +void RegisterJob::callFinishedSlot(QDBusPendingCallWatcher* watcher) { + if (watcher->isError()) { + setError(-1); + setErrorText(watcher->error().message()); + } + + // notify result delayed + QTimer::singleShot(1000, this, &RegisterJob::emitResult); +} diff --git a/src/bin/RegisterJob.h b/src/bin/RegisterJob.h new file mode 100644 index 0000000000000000000000000000000000000000..6de4b60993180c55825d68238bc39cbeb56f0763 --- /dev/null +++ b/src/bin/RegisterJob.h @@ -0,0 +1,24 @@ +#pragma once + +// libraries +#include +#include + +class QDBusPendingCallWatcher; + +class OrgAppimageServices1LauncherInterface; + +class RegisterJob : public KJob { +public: + RegisterJob(const QString& target, QObject* parent = nullptr); + + void start() override; + +protected slots: + + void callFinishedSlot(QDBusPendingCallWatcher* watcher); + +private: + QString target; + OrgAppimageServices1LauncherInterface* launcherInterface; +}; diff --git a/src/bin/RemoveJob.cpp b/src/bin/RemoveJob.cpp index 04268f046960ac88eb7002d86d0856f9a9cc610a..383fbcf92a7856f537afb782577247235e08a9f8 100644 --- a/src/bin/RemoveJob.cpp +++ b/src/bin/RemoveJob.cpp @@ -6,16 +6,19 @@ #include "LauncherInterface.h" RemoveJob::RemoveJob(const QString& target, QObject* parent) - : KJob(parent), target(target), - launcherInterface(new OrgAppimageServices1LauncherInterface("org.appimage.Services1.Launcher", - "/org/appimage/Services1/Launcher", - QDBusConnection::sessionBus(), this)) {} + : KJob(parent), target(target), + launcherInterface(new OrgAppimageServices1LauncherInterface("org.appimage.Services1.Launcher", + "/org/appimage/Services1/Launcher", + QDBusConnection::sessionBus(), this)) {} void RemoveJob::start() { + description(this, i18n("Removing launcher entry"), + qMakePair(i18nc("Target AppImage", "Application"), target)); + auto reply = launcherInterface->unregisterApp(target); if (reply.isError()) { setError(-1); - setErrorText(i18n("Remove failed: %0").arg(reply.error().message())); + setErrorText(reply.error().message()); } QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); @@ -26,9 +29,9 @@ void RemoveJob::start() { void RemoveJob::callFinishedSlot(QDBusPendingCallWatcher* watcher) { if (watcher->isError()) { setError(-1); - setErrorText(i18n("Remove failed: %0").arg(watcher->error().message())); + setErrorText(watcher->error().message()); } else - description(this, i18n("Application successfully removed")); + infoMessage(this, i18n("Entry removed")); // notify result delayed QTimer::singleShot(1000, this, &RemoveJob::emitResult); diff --git a/src/bin/TargetDataLoader.cpp b/src/bin/TargetDataLoader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0dbcad8cdf0de21fd937917a04f37513f5730744 --- /dev/null +++ b/src/bin/TargetDataLoader.cpp @@ -0,0 +1,81 @@ +// libraries +#include +#include + +// local +#include "InspectorInterface.h" +#include "TargetDataLoader.h" + +TargetDataLoader::TargetDataLoader(QString target, QObject* parent) : QObject(parent), target(std::move(target)) {} + +void TargetDataLoader::loadTargetDataIntoApplication() { + auto inspectorInterface = new OrgAppimageServices1InspectorInterface("org.appimage.Services1.Inspector", + "/org/appimage/Services1/Inspector", + QDBusConnection::sessionBus(), this); + loadApplicationIcon(inspectorInterface); + loadApplicationName(inspectorInterface); +} + +void TargetDataLoader::loadApplicationName(OrgAppimageServices1InspectorInterface* inspectorInterface) const { + auto reply = inspectorInterface->getApplicationInfo(target); + if (reply.isError()) + qWarning() << "Unable to fetch AppImage information " << reply.error().message(); + + else { + QString response = reply.value(); + QJsonDocument document = QJsonDocument::fromJson(response.toLocal8Bit()); + QJsonObject root = document.object(); + + QString nameValue = root.value("name").toString(); + if (!nameValue.isEmpty()) + QApplication::setApplicationName(nameValue); + } +} + +void TargetDataLoader::loadApplicationIcon(OrgAppimageServices1InspectorInterface* inspectorInterface) { + QTemporaryFile temporaryIconFile("XXXXXX"); + if (temporaryIconFile.open()) { + auto reply = inspectorInterface->extractApplicationIcon(target, temporaryIconFile.fileName()); + if (reply.isError()) + qWarning() << "Unable to fetch AppImage icon " << reply.error().message(); + + QString themeIconName = "application-vnd.appimage"; + QImage applicationIcon(temporaryIconFile.fileName()); + if (!applicationIcon.isNull()) { + // scale the icon to a valid icon size + applicationIcon = applicationIcon.scaled(512, 512, Qt::KeepAspectRatio); + + // prepare target dir + QString dirPath = QDir::homePath() + "/.local/share/icons/hicolor/%1x%1/apps/"; + dirPath = dirPath.arg(applicationIcon.height()); + QDir::home().mkpath(dirPath); + + // prepare file name + QFileInfo temporaryIconFileInfo(temporaryIconFile); + QString fileName = "plasma-appimage-integration_%2.png"; + fileName = fileName.arg(temporaryIconFileInfo.baseName()); + + // save the icon to the right place on the hicolor theme to make it accessible from the kjob dialog + temporaryAppIconPath = dirPath + fileName; + if (applicationIcon.save(temporaryAppIconPath, "PNG")) { + // use the temporary created icon + themeIconName = "plasma-appimage-integration_" + temporaryIconFileInfo.baseName(); + } else { + qWarning() << "unable so save icon to " << temporaryAppIconPath; + } + } + + // double check that the icon was properly registered in the theme before using it + if (QIcon::hasThemeIcon(themeIconName)) { + QApplication::setWindowIcon(QIcon::fromTheme(themeIconName)); + } else { + qDebug() << "Icon not found in theme " << themeIconName; + QApplication::setWindowIcon(QIcon::fromTheme("application-vnd.appimage")); + } + } +} + +TargetDataLoader::~TargetDataLoader() { + if (QFile::exists(temporaryAppIconPath)) + QFile::remove(temporaryAppIconPath); +} diff --git a/src/bin/TargetDataLoader.h b/src/bin/TargetDataLoader.h new file mode 100644 index 0000000000000000000000000000000000000000..795370321b241ee23cfe1240622149d6d34fdd5f --- /dev/null +++ b/src/bin/TargetDataLoader.h @@ -0,0 +1,25 @@ +#pragma once + +// libraries +#include +#include + +class OrgAppimageServices1InspectorInterface; + +class TargetDataLoader : public QObject { +Q_OBJECT +public: + explicit TargetDataLoader(QString target, QObject* parent = nullptr); + + ~TargetDataLoader() override; + + void loadTargetDataIntoApplication(); + +private: + QString target; + QString temporaryAppIconPath; + + void loadApplicationIcon(OrgAppimageServices1InspectorInterface* inspectorInterface); + + void loadApplicationName(OrgAppimageServices1InspectorInterface* inspectorInterface) const; +}; \ No newline at end of file diff --git a/src/bin/UninstallJob.cpp b/src/bin/UninstallJob.cpp index 4b9ffd6d8273cc3023b7729622282578ff666f72..fc81325c1aec041165bec99ffe13a4d2b355967c 100644 --- a/src/bin/UninstallJob.cpp +++ b/src/bin/UninstallJob.cpp @@ -15,8 +15,8 @@ void UninstallJob::start() { qDebug() << "calling pkexec appimage-services install " << target; connect(&process, SIGNAL(finished(int)), this, SLOT(onProcessFinished(int))); - description(this, i18n("Uninstalling Application"), - qMakePair(i18nc("The AppImage being uninstalled", "Source"), target)); + description(this, i18n("Uninstalling application"), + qMakePair(i18nc("The AppImage being uninstalled", "Application"), target)); process.start(); } @@ -27,6 +27,8 @@ void UninstallJob::onProcessFinished(int exitCode) { if (exitCode != 0) { setError(exitCode); setErrorText(process.readAllStandardError()); + } else { + infoMessage(this, i18n("Application uninstalled")); } // notify result delayed diff --git a/src/bin/UpdateJob.cpp b/src/bin/UpdateJob.cpp index c3d26f24b8b0577cf857b59ee377a664ba536aa3..cdbfb0561f082088521fdf58029116186f132f32 100644 --- a/src/bin/UpdateJob.cpp +++ b/src/bin/UpdateJob.cpp @@ -1,6 +1,4 @@ // libraries -#include -#include #include #include #include @@ -25,16 +23,13 @@ void UpdateJob::onBytesReceivedChanged(int value) { void UpdateJob::onStateChanged(int state) { switch (state) { case 10: - description(this, i18nc("Job heading, like 'Copying'", "Reading update data"), - qMakePair(i18nc("The AppImage being updated", "Source"), target)); + infoMessage(this, i18nc("Job heading, like 'Copying'", "Reading update data")); break; case 20: - description(this, i18nc("Job heading, like 'Copying'", "Looking for updates"), - qMakePair(i18nc("The AppImage being updated", "Source"), target)); + infoMessage(this, i18nc("Job heading, like 'Copying'", "Looking for updates")); break; case 30: - description(this, i18nc("Job heading, like 'Copying'", "Downloading"), - qMakePair(i18nc("The AppImage being updated", "Source"), target)); + infoMessage(this, i18nc("Job heading, like 'Copying'", "Downloading update")); break; // final states case 21: @@ -58,6 +53,9 @@ bool UpdateJob::doKill() { } void UpdateJob::start() { + description(this, i18nc("Job heading, like 'Copying'", "Updating application"), + qMakePair(i18nc("The AppImage being updated", "Application"), target)); + if (error() == 0) connectUpdaterInterface(); @@ -146,15 +144,6 @@ void UpdateJob::onError(int errorCode) { setErrorText(errorTitle); } -void UpdateJob::notifyError(const QString& title, const QString& message, QWidget* parentWidget) { - KNotification* notify = new KNotification(QStringLiteral("notification"), parentWidget, - KNotification::CloseOnTimeout | KNotification::DefaultEvent); - notify->setTitle(title); - notify->setText(message); - notify->setIconName("dialog-warning"); - notify->sendEvent(); -} - void UpdateJob::emitResultDelayed() { QTimer::singleShot(400, [this]() { emitResult(); diff --git a/src/bin/UpdateJob.h b/src/bin/UpdateJob.h index 79f09c4a91a720f0886f8442f6814982b4e263a3..a9a7a727ae99a442009a061dcbb3f0636e844703 100644 --- a/src/bin/UpdateJob.h +++ b/src/bin/UpdateJob.h @@ -20,8 +20,6 @@ public: void start() override; - static void notifyError(const QString& title, const QString& message, QWidget* parentWidget = nullptr); - protected slots: void onBytesTotalChanged(int total); diff --git a/src/bin/main.cpp b/src/bin/main.cpp index 22b809c0be0370ed84a768463c13b97841064b57..9744618b65b9e7bca1b90370404478fe5f94b440 100644 --- a/src/bin/main.cpp +++ b/src/bin/main.cpp @@ -10,9 +10,11 @@ // local #include "UpdateJob.h" +#include "RegisterJob.h" #include "RemoveJob.h" #include "InstallJob.h" #include "UninstallJob.h" +#include "TargetDataLoader.h" QString parseTarget(QCommandLineParser& parser) { @@ -32,41 +34,6 @@ QString parseTarget(QCommandLineParser& parser) { } } -void executeUpdateCommand(const QString& target) { - KJob* job = new UpdateJob(target); - - KIO::getJobTracker()->registerJob(job); - job->start(); - - if (job->error() != 0) - UpdateJob::notifyError(i18n("Update failed").arg(target), job->errorString()); -} - -void executeRemoveCommand(const QString& target) { - KJob* job = new RemoveJob(target); - - KIO::getJobTracker()->registerJob(job); - job->start(); - - if (job->error() != 0) - UpdateJob::notifyError(i18n("Remove failed").arg(target), job->errorString()); - -} - -void executeInstallCommand(const QString& target) { - KJob* job = new InstallJob(target); - - KIO::getJobTracker()->registerJob(job); - job->start(); -} - -void executeUninstallCommand(const QString& target) { - KJob* job = new UninstallJob(target); - - KIO::getJobTracker()->registerJob(job); - job->start(); -} - int main(int argc, char** argv) { QApplication app(argc, argv); QApplication::setApplicationName("plasma-appimage-integration"); @@ -88,25 +55,43 @@ int main(int argc, char** argv) { const QString& command = positionalArguments.at(0); + QString target; + KJob* job = nullptr; if (command == "update") { - QString target = parseTarget(parser); - executeUpdateCommand(target); + target = parseTarget(parser); + job = new UpdateJob(target); } if (command == "remove") { - QString target = parseTarget(parser); - executeRemoveCommand(target); + target = parseTarget(parser); + job = new RemoveJob(target); + } + + if (command == "register") { + target = parseTarget(parser); + job = new RegisterJob(target); } if (command == "install") { - QString target = parseTarget(parser); - executeInstallCommand(target); + target = parseTarget(parser); + job = new InstallJob(target); } if (command == "uninstall") { - QString target = parseTarget(parser); - executeUninstallCommand(target); + target = parseTarget(parser); + job = new UninstallJob(target); } + + if (!target.isEmpty()) { + auto targetDataLoader = new TargetDataLoader(target, &app); + targetDataLoader->loadTargetDataIntoApplication(); + } + + if (job != nullptr) { + KIO::getJobTracker()->registerJob(job); + QMetaObject::invokeMethod(job, "start"); + } + return QApplication::exec(); } diff --git a/src/fileitem-actions/AppImageFileItemActions.cpp b/src/fileitem-actions/AppImageFileItemActions.cpp index 8552cffc442e0fab712685968713261b9d4b8c38..60094c50589e1276b5337c9f4fe85c4187ccdbf0 100644 --- a/src/fileitem-actions/AppImageFileItemActions.cpp +++ b/src/fileitem-actions/AppImageFileItemActions.cpp @@ -94,32 +94,26 @@ QAction *AppImageFileItemActions::createInstallAction(const KFileItemListPropert void AppImageFileItemActions::addToMenu() { const QList urls = sender()->property("urls").value>(); - QWidget* parentWidget = sender()->property("parentWidget").value(); - - QList> replies; - for (const QUrl& url : urls) - replies += launcherInterface->registerApp(url.toString()); - - QString errorTitle = i18n("Add to launcher failed"); - for (QDBusPendingReply& reply: replies) { - reply.waitForFinished(); - - if (reply.isError()) - showErrorMessage(errorTitle, reply.error().message(), parentWidget); - else { - // notify failed operation - if (!reply.value()) { - QString url = urls.at(replies.indexOf(reply)).toString(); - showErrorMessage(errorTitle, i18n("\"%0\"\nthe file seems broken").arg(url), parentWidget); - } - } + QString program = "plasma-appimage-integration"; + + for (const QUrl& url : urls) { + QStringList arguments; + arguments << "register" << url.toLocalFile(); + + QProcess::startDetached(program, arguments); } } void AppImageFileItemActions::removeFromMenu() { const QList urls = sender()->property("urls").value>(); - for (const QUrl& url : urls) - launcherInterface->unregisterApp(url.toString()); + QString program = "plasma-appimage-integration"; + + for (const QUrl& url : urls) { + QStringList arguments; + arguments << "remove" << url.toLocalFile(); + + QProcess::startDetached(program, arguments); + } } void AppImageFileItemActions::update() {