-
Notifications
You must be signed in to change notification settings - Fork 287
Feature/improve desktop integration dialog #400
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
Changes from all commits
158f2e8
c85bcda
9b8a91a
7857807
125f2eb
5882702
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} |
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 |
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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> | ||
<html><head><meta name="qrichtext" content="1" /><style type="text/css"> | ||
p, li { white-space: pre-wrap; } | ||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> | ||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">%1 has not been integrated into your system.</p> | ||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> <br />Integrating it will move the AppImage into a predefined location, and include it in your application launcher.</p> | ||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> | ||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To remove or update the AppImage, please use the context menu of the application icon in your task bar or launcher. </p> | ||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> | ||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The directory where the integrated AppImages are stored in is currently set to: %2</p></body></html></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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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) { | ||
|
@@ -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 | ||
|
@@ -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 { | ||
|
@@ -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()); | ||
|
||
|
@@ -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()); | ||
|
||
|
@@ -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 | ||
); | ||
|
||
|
@@ -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(); | ||
|
||
// 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I'm curious: why are you calling the dialog's There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.