Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/improve desktop integration dialog #400

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ui/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
if(NOT BUILD_LITE)
# main AppImageLauncher application
add_executable(AppImageLauncher main.cpp resources.qrc first-run.cpp first-run.h first-run.ui)
add_executable(AppImageLauncher main.cpp resources.qrc first-run.cpp first-run.h first-run.ui integration_dialog.cpp integration_dialog.h integration_dialog.ui)
target_link_libraries(AppImageLauncher shared PkgConfig::glib libappimage shared)

# set binary runtime rpath to make sure the libappimage.so built and installed by this project is going to be used
Expand Down
56 changes: 56 additions & 0 deletions src/ui/integration_dialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// system includes
#include <sstream>
#include <utility>

// library includes
#include <QStyle>

// local headers
#include "integration_dialog.h"
#include "ui_integration_dialog.h"

IntegrationDialog::IntegrationDialog(QString pathToAppImage, QString integratedAppImagesDestinationPath,
QWidget* parent) :
QDialog(parent), ui(new Ui::IntegrationDialog),
pathToAppImage(std::move(pathToAppImage)),
integratedAppImagesDestinationPath(std::move(integratedAppImagesDestinationPath)) {
ui->setupUi(this);

setIcon();
setMessage();

QObject::connect(ui->pushButtonIntegrateAndRun, &QPushButton::released, this,
&IntegrationDialog::onPushButtonIntegrateAndRunReleased);
QObject::connect(ui->pushButtonRunOnce, &QPushButton::released, this,
&IntegrationDialog::onPushButtonRunOnceReleased);
}

void IntegrationDialog::setMessage() {
QString message = ui->message->text();
message = message.arg(pathToAppImage, integratedAppImagesDestinationPath);
ui->message->setText(message);
}

void IntegrationDialog::setIcon() {
QIcon icon = QIcon(":/AppImageLauncher.svg");
QPixmap pixmap = icon.pixmap(QSize(64, 64));
ui->icon->setPixmap(pixmap);
}

IntegrationDialog::~IntegrationDialog() {
delete ui;
}

void IntegrationDialog::onPushButtonIntegrateAndRunReleased() {
this->resultAction = ResultingAction::IntegrateAndRun;
this->accept();
}

void IntegrationDialog::onPushButtonRunOnceReleased() {
this->resultAction = ResultingAction::RunOnce;
this->accept();
}

IntegrationDialog::ResultingAction IntegrationDialog::getResultAction() const {
return resultAction;
}
44 changes: 44 additions & 0 deletions src/ui/integration_dialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef APPIMAGELAUNCHER_INTEGRATION_DIALOG_H
#define APPIMAGELAUNCHER_INTEGRATION_DIALOG_H

// library includes
#include <QDialog>

QT_BEGIN_NAMESPACE
namespace Ui { class IntegrationDialog; }
QT_END_NAMESPACE

class IntegrationDialog : public QDialog {
Q_OBJECT

public:
enum ResultingAction {
IntegrateAndRun,
RunOnce
};

explicit IntegrationDialog(QString pathToAppImage, QString integratedAppImagesDestinationPath,
QWidget* parent = nullptr);

~IntegrationDialog() override;

ResultingAction getResultAction() const;

protected:
Q_SLOT void onPushButtonIntegrateAndRunReleased();

Q_SLOT void onPushButtonRunOnceReleased();

ResultingAction resultAction;

private:
Ui::IntegrationDialog* ui;
QString pathToAppImage;
QString integratedAppImagesDestinationPath;

void setIcon();

void setMessage();
};

#endif //APPIMAGELAUNCHER_INTEGRATION_DIALOG_H
125 changes: 125 additions & 0 deletions src/ui/integration_dialog.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IntegrationDialog</class>
<widget class="QDialog" name="IntegrationDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>460</width>
<height>300</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Desktop Integration</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Icon</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="margin">
<number>12</number>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="message">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;%1 has not been integrated into your system.&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; &lt;br /&gt;Integrating it will move the AppImage into a predefined location, and include it in your application launcher.&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To remove or update the AppImage, please use the context menu of the application icon in your task bar or launcher. &lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The directory where the integrated AppImages are stored in is currently set to: %2&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButtonIntegrateAndRun">
<property name="text">
<string>Integrate and run</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonRunOnce">
<property name="text">
<string>Run once</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
95 changes: 34 additions & 61 deletions src/ui/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
#include <fstream>
#include <iostream>
#include <sstream>

extern "C" {
#include <sys/stat.h>
#include <libgen.h>
#include <unistd.h>
#include <glib.h>
#include <sys/stat.h>
#include <libgen.h>
#include <unistd.h>
#include <glib.h>
}

// library includes
Expand All @@ -22,17 +23,17 @@ extern "C" {
#include <QPushButton>
#include <QRegularExpression>
#include <QString>
#include <QTemporaryDir>
#include <QTextStream>

extern "C" {
#include <appimage/appimage.h>
#include <appimage/appimage.h>
}

// local headers
#include "shared.h"
#include "trashbin.h"
#include "translationmanager.h"
#include "first-run.h"
#include "integration_dialog.h"

// Runs an AppImage. Returns suitable exit code for main application.
int runAppImage(const QString& pathToAppImage, unsigned long argc, char** argv) {
Expand Down Expand Up @@ -161,7 +162,8 @@ int main(int argc, char** argv) {

std::ostringstream usage;
usage << QObject::tr("Usage: %1 [options] <path>").arg(argv[0]).toStdString() << std::endl
<< QObject::tr("Desktop integration helper for AppImages, for use by Linux distributions.").toStdString() << std::endl
<< QObject::tr("Desktop integration helper for AppImages, for use by Linux distributions.").toStdString()
<< std::endl
<< std::endl
<< QObject::tr("Options:").toStdString() << std::endl
<< " --appimagelauncher-help " << QObject::tr("Display this help and exit").toStdString() << std::endl
Expand Down Expand Up @@ -256,7 +258,8 @@ int main(int argc, char** argv) {
auto config = getConfig();

// assumes defaults if config doesn't exist or lacks the related key(s)
if (config == nullptr || !config->contains("AppImageLauncher/enable_daemon") || config->value("AppImageLauncher/enable_daemon").toBool()) {
if (config == nullptr || !config->contains("AppImageLauncher/enable_daemon") ||
config->value("AppImageLauncher/enable_daemon").toBool()) {
system("systemctl --user enable appimagelauncherd.service");
system("systemctl --user start appimagelauncherd.service");
} else {
Expand Down Expand Up @@ -288,7 +291,8 @@ int main(int argc, char** argv) {
// check for X-AppImage-Integrate=false
auto shallNotBeIntegrated = appimage_shall_not_be_integrated(pathToAppImage.toStdString().c_str());
if (shallNotBeIntegrated < 0)
std::cerr << "AppImageLauncher error: appimage_shall_not_be_integrated() failed (returned " << shallNotBeIntegrated << ")" << std::endl;
std::cerr << "AppImageLauncher error: appimage_shall_not_be_integrated() failed (returned "
<< shallNotBeIntegrated << ")" << std::endl;
else if (shallNotBeIntegrated > 0)
return runAppImage(pathToAppImage, appImageArgv.size(), appImageArgv.data());

Expand All @@ -299,7 +303,8 @@ int main(int argc, char** argv) {
// ignore terminal apps (fixes #2)
auto isTerminalApp = appimage_is_terminal_app(pathToAppImage.toStdString().c_str());
if (isTerminalApp < 0)
std::cerr << "AppImageLauncher error: appimage_is_terminal_app() failed (returned " << isTerminalApp << ")" << std::endl;
std::cerr << "AppImageLauncher error: appimage_is_terminal_app() failed (returned " << isTerminalApp << ")"
<< std::endl;
else if (isTerminalApp > 0)
return runAppImage(pathToAppImage, appImageArgv.size(), appImageArgv.data());

Expand Down Expand Up @@ -386,9 +391,9 @@ int main(int argc, char** argv) {
"Choosing No will run the AppImage once, and leave the AppImage in its current "
"directory."
"\n\n").arg(pathToAppImage) +
// translate separately to share string with the other dialog
QObject::tr("The directory the integrated AppImages are stored in is currently set to:\n"
"%1").arg(integratedAppImagesDestination().path()) + "\n",
// translate separately to share string with the other dialog
QObject::tr("The directory the integrated AppImages are stored in is currently set to:\n"
"%1").arg(integratedAppImagesDestination().path()) + "\n",
QMessageBox::Yes | QMessageBox::No
);

Expand All @@ -414,56 +419,24 @@ int main(int argc, char** argv) {
}
}

std::ostringstream explanationStrm;
explanationStrm << QObject::tr("Integrating it will move the AppImage into a predefined location, "
"and include it in your application launcher.").toStdString() << std::endl
<< std::endl
<< QObject::tr("To remove or update the AppImage, please use the context menu of the "
"application icon in your task bar or launcher.").toStdString() << std::endl
<< std::endl
<< QObject::tr("The directory the integrated AppImages are stored in is currently "
"set to:").toStdString() << std::endl
<< integratedAppImagesDestination().path().toStdString() << std::endl;

auto explanation = explanationStrm.str();

std::ostringstream messageStrm;
messageStrm << QObject::tr("%1 has not been integrated into your system.").arg(pathToAppImage).toStdString() << "\n\n"
<< QObject::tr(explanation.c_str()).toStdString();

auto* messageBox = new QMessageBox(
QMessageBox::Question,
QObject::tr("Desktop Integration"),
QString::fromStdString(messageStrm.str())
);

auto* okButton = messageBox->addButton(QObject::tr("Integrate and run"), QMessageBox::AcceptRole);
auto* runOnceButton = messageBox->addButton(QObject::tr("Run once"), QMessageBox::ApplyRole);

// *whyever* Qt somehow needs a button with "RejectRole" or the X button won't close the current window...
auto* cancelButton = messageBox->addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
// ... but it is fine to hide that button after creating it, so it's not displayed
cancelButton->hide();

messageBox->setDefaultButton(QMessageBox::Ok);
QString integratedAppImagesDestinationPath = integratedAppImagesDestination().path();
auto integrationDialog = new IntegrationDialog(pathToAppImage, integratedAppImagesDestinationPath);
integrationDialog->show();
TheAssassin marked this conversation as resolved.
Show resolved Hide resolved

// cannot use messageBox.exec(), will produce SEGFAULTS as QCoreApplications can't show message boxes
messageBox->show();
// As the integration dialog is the only window in our application we can safely use its exec method
integrationDialog->exec();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm curious: why are you calling the dialog's exec() now? I'm pretty sure you used to call QApplication::exec().

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenario the QDialog is the only window and the only part of the code that relies on the QEventsLoop. Which meas that once the dialog exits we will not required the events loop enymore.

The QDialog::exec method starts an evenstloop for its sole use, therefore is a good fit. We would have to use the QAppliaction::exec if there were other events (signals/slots) to be processed after the dialog is closed.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly what I was thinking, too. Thanks for clarifying. We can always fix/improve if we get rid of the solely synchronous workflow (although I'm sure there's higher priorities).


// don't need to cast around, exec() is a static method anyway, and QApplication is a singleton
QApplication::exec();

const auto* clickedButton = messageBox->clickedButton();

if (clickedButton == okButton) {
return integrateAndRunAppImage();
} else if (clickedButton == runOnceButton) {
return runAppImage(pathToAppImage, appImageArgv.size(), appImageArgv.data());
} else if (clickedButton == cancelButton) {
if (integrationDialog->result() == QDialog::Rejected)
return 0;
}

// _should_ be unreachable
return 1;
switch (integrationDialog->getResultAction()) {
case IntegrationDialog::IntegrateAndRun:
return integrateAndRunAppImage();
case IntegrationDialog::RunOnce:
return runAppImage(pathToAppImage, appImageArgv.size(), appImageArgv.data());
default:
displayError(QObject::tr("Unexpected result from the integration dialog."));
return 1;
}
}