Commit 0655f4f0 authored by azubieta's avatar azubieta
Browse files

Merge branch 'development' into 'master'

Add target applciation icon and name to the job dialog

See merge request !8
parents 16fb0ece a1015c66
Pipeline #676 passed with stage
in 5 minutes and 52 seconds
......@@ -5,6 +5,11 @@
<arg type="s" direction="out"/>
<arg name="appImagePath" type="s" direction="in"/>
</method>
<method name="extractApplicationIcon">
<arg type="b" direction="out"/>
<arg name="appImagePath" type="s" direction="in"/>
<arg name="targetPath" type="s" direction="in"/>
</method>
<method name="listContents">
<arg type="as" direction="out"/>
<arg name="appImagePath" type="s" direction="in"/>
......
......@@ -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)
......
......@@ -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<QString, QString>(i18nc("The AppImage being installed", "Source"), target));
description(this, i18n("Installing application"),
qMakePair<QString, QString>(i18nc("The AppImage being installed", "Application"), target));
process.start();
}
......
// libraries
#include <KLocalizedString>
// 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<QString, QString>(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);
}
#pragma once
// libraries
#include <KJob>
#include <QUrl>
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;
};
......@@ -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<QString, QString>(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);
......
// libraries
#include <QIcon>
#include <QtWidgets/QApplication>
// 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);
}
#pragma once
// libraries
#include <QtCore/QString>
#include <QtCore/QObject>
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
......@@ -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<QString, QString>(i18nc("The AppImage being uninstalled", "Source"), target));
description(this, i18n("Uninstalling application"),
qMakePair<QString, QString>(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
......
// libraries
#include <QFile>
#include <QDebug>
#include <KLocalizedString>
#include <KNotification>
#include <QTimer>
......@@ -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<QString, QString>(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<QString, QString>(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<QString, QString>(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<QString, QString>(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();
......
......@@ -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);
......
......@@ -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();
}
......
......@@ -94,32 +94,26 @@ QAction *AppImageFileItemActions::createInstallAction(const KFileItemListPropert
void AppImageFileItemActions::addToMenu() {
const QList<QUrl> urls = sender()->property("urls").value<QList<QUrl>>();
QWidget* parentWidget = sender()->property("parentWidget").value<QWidget*>();
QList<QDBusPendingReply<bool>> replies;
for (const QUrl& url : urls)
replies += launcherInterface->registerApp(url.toString());
QString errorTitle = i18n("Add to launcher failed");
for (QDBusPendingReply<bool>& 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<QUrl> urls = sender()->property("urls").value<QList<QUrl>>();
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() {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment