Skip to content
Open
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
64 changes: 64 additions & 0 deletions resources/fallback-icons/application-vnd.appimage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/fallback-icons/document-new.svg
1 change: 1 addition & 0 deletions resources/fallback-icons/folder-new.svg
82 changes: 61 additions & 21 deletions src/shared/shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ void createConfigFile(int askToMove,
const QString& destination,
int enableDaemon,
const QStringList& additionalDirsToWatch,
int monitorMountedFilesystems) {
int monitorMountedFilesystems,
const QStringList& excludePaths) {
auto configFilePath = getConfigFilePath();

QFile file(configFilePath);
Expand Down Expand Up @@ -151,6 +152,14 @@ void createConfigFile(int askToMove,
file.write("\n");
}

if (excludePaths.empty()) {
file.write("# exclude_paths = /opt/appimages/:/even/more/app-images.AppImage\n");
} else {
file.write("exclude_paths = ");
file.write(excludePaths.join(':').toUtf8());
file.write("\n");
}

file.write("\n\n");

// daemon configs
Expand Down Expand Up @@ -400,6 +409,29 @@ bool shallMonitorMountedFilesystems(const QSettings* config) {
return config->value("appimagelauncherd/monitor_mounted_filesystems", "false").toBool();
}

bool sanitizePath(QString& path) {
// empty values will, for some reason, be interpreted as "use the home directory"
// as we don't want to accidentally monitor the home directory, we need to skip those values
if (path.isEmpty()) {
qDebug() << "skipping empty directory path";
return false;
}

// make sure to have full path
qDebug() << "path before tilde expansion:" << path;
path = expandTilde(path);
qDebug() << "path after tilde expansion:" << path;

// non-absolute paths which don't contain a tilde cannot be resolved safely, they likley depend on the cwd
// therefore, we need to ignore those
if (!QFileInfo(path).isAbsolute()) {
std::cerr << "Warning: path " << path.toStdString() << " can not be resolved, skipping" << std::endl;
return false;
}

return true;
}

QDirSet getAdditionalDirectoriesFromConfig(const QSettings* config) {
Q_ASSERT(config != nullptr);

Expand All @@ -410,33 +442,16 @@ QDirSet getAdditionalDirectoriesFromConfig(const QSettings* config) {
QDirSet additionalDirs{};

for (auto dirPath : configValue.split(":")) {
// empty values will, for some reason, be interpreted as "use the home directory"
// as we don't want to accidentally monitor the home directory, we need to skip those values
if (dirPath.isEmpty()) {
qDebug() << "skipping empty directory path";
continue;
}

// make sure to have full path
qDebug() << "path before tilde expansion:" << dirPath;
dirPath = expandTilde(dirPath);
qDebug() << "path after tilde expansion:" << dirPath;

// non-absolute paths which don't contain a tilde cannot be resolved safely, they likley depend on the cwd
// therefore, we need to ignore those
if (!QFileInfo(dirPath).isAbsolute()) {
std::cerr << "Warning: path " << dirPath.toStdString() << " can not be resolved, skipping" << std::endl;
if(!sanitizePath(dirPath)) {
continue;
}

const QDir dir(dirPath);

if (!dir.exists()) {
if (!QDir(dirPath).exists()) {
std::cerr << "Warning: could not find directory " << dirPath.toStdString() << ", skipping" << std::endl;
continue;
}

additionalDirs.insert(dir);
additionalDirs.insert(dirPath);
}

return additionalDirs;
Expand Down Expand Up @@ -1370,3 +1385,28 @@ void setUpFallbackIconPaths(QWidget* parent) {
button->setIcon(newIcon);
}
}


bool isPathExcluded(const QSettings* config, const QString& path) {
Q_ASSERT(config != nullptr);

constexpr auto configKey = "AppImageLauncher/exclude_paths";
const auto configValue = config->value(configKey, "").toString();
qDebug() << configKey << "value:" << configValue;

for (auto excludePath : configValue.split(":")) {
if(!sanitizePath(excludePath)) {
continue;
}

if (QFileInfo(excludePath) == QFileInfo(path)) {
return true;
}

if (QDir(excludePath).exists() && QFileInfo(path).absoluteFilePath().startsWith(excludePath)) {
return true;
}
}

return false;
}
5 changes: 4 additions & 1 deletion src/shared/shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ IntegrationState integrateAppImage(const QString& pathToAppImage, const QString&
// < 0: unset; 0 = false; > 0 = true
// destination is a string that, when empty, will be interpreted as "use default"
void createConfigFile(int askToMove, const QString& destination, int enableDaemon,
const QStringList& additionalDirsToWatch = {}, int monitorMountedFilesystems = -1);
const QStringList& additionalDirsToWatch = {}, int monitorMountedFilesystems = -1, const QStringList& excludePaths = {});

// replaces ~ character in paths with real home directory, if necessary and possible
QString expandTilde(QString path);
Expand Down Expand Up @@ -131,3 +131,6 @@ QIcon loadIconWithFallback(const QString& iconName);

// sets up paths to fallback icons bundled with AppImageLauncher
void setUpFallbackIconPaths(QWidget*);

// Check if a given path is in the exclude list
bool isPathExcluded(const QSettings* config, const QString& path);
4 changes: 4 additions & 0 deletions src/ui/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ int main(int argc, char** argv) {
system("systemctl --user stop appimagelauncherd.service");
}

if (isPathExcluded(config, pathToAppImage)) {
return runAppImage(pathToAppImage, appImageArgv.size(), appImageArgv.data());
}

// beyond the next block, the code requires a UI
// as we don't want to offer integration over a headless connection, we just run the AppImage
if (isHeadless()) {
Expand Down
128 changes: 127 additions & 1 deletion src/ui/settings_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Se
connect(ui->additionalDirsRemoveButton, &QToolButton::released, this, &SettingsDialog::onRemoveDirectoryToWatchButtonClicked);
connect(ui->additionalDirsListWidget, &QListWidget::itemActivated, this, &SettingsDialog::onDirectoryToWatchItemActivated);
connect(ui->additionalDirsListWidget, &QListWidget::itemClicked, this, &SettingsDialog::onDirectoryToWatchItemActivated);
connect(ui->excludeAddDirectoryButton, &QToolButton::released, this, &SettingsDialog::onAddExcludeDirButtonClicked);
connect(ui->excludeAddFileButton, &QToolButton::released, this, &SettingsDialog::onAddExcludeFileButtonClicked);
connect(ui->excludeRemoveButton, &QToolButton::released, this, &SettingsDialog::onRemoveExcludeButtonClicked);
connect(ui->excludeListWidget, &QListWidget::itemActivated, this, &SettingsDialog::onExcludeItemActivated);
connect(ui->excludeListWidget, &QListWidget::itemClicked, this, &SettingsDialog::onExcludeItemActivated);

QStringList availableFeatures;

Expand Down Expand Up @@ -96,6 +101,48 @@ void SettingsDialog::addDirectoryToWatchToListView(const QString& dirPath) {
ui->additionalDirsListWidget->addItem(item);
}


void SettingsDialog::addExcludeToListView(const QString& fileOrDirPath) {
// empty paths are not permitted
if (fileOrDirPath.isEmpty())
return;

const QFileInfo file(fileOrDirPath);

// // we don't want to redundantly add the main integration directory
// if (dir == integratedAppImagesDestination())
// return;

QIcon icon;

auto findIcon = [](const std::initializer_list<QString>& names) {
for (const auto& i : names) {
auto icon = QIcon::fromTheme(i, loadIconWithFallback(i));

if (!icon.isNull())
return icon;
}

return QIcon{};
};

if (file.isFile()) {
icon = findIcon({"application-vnd.appimage"});
} else if (file.isDir()) {
icon = findIcon({"folder"});
} else {
// TODO: search for more meaningful icon, "remove" doesn't really show the directory is missing
icon = findIcon({"remove"});
}

if (icon.isNull()) {
qDebug() << "item icon unavailable, using fallback";
}

auto* item = new QListWidgetItem(icon, fileOrDirPath);
ui->excludeListWidget->addItem(item);
}

void SettingsDialog::loadSettings() {
const auto daemonIsEnabled = settingsFile->value("AppImageLauncher/enable_daemon", "true").toBool();
const auto askMoveChecked = settingsFile->value("AppImageLauncher/ask_to_move", "true").toBool();
Expand All @@ -109,6 +156,11 @@ void SettingsDialog::loadSettings() {
for (const auto& dirPath : additionalDirsPath.split(":")) {
addDirectoryToWatchToListView(dirPath);
}

const auto excludePaths = settingsFile->value("AppImageLauncher/exclude_paths", "").toString();
for (const auto& excludePath : excludePaths.split(":")) {
addExcludeToListView(excludePath);
}
}
}

Expand All @@ -128,6 +180,16 @@ void SettingsDialog::saveSettings() {
}
}

QStringList excludePaths;

{
QListWidgetItem* currentItem;

for (int i = 0; (currentItem = ui->excludeListWidget->item(i)) != nullptr; ++i) {
excludePaths << currentItem->text();
}
}

// temporary workaround to fill in the monitorMountedFilesystems with the same value it had in the old settings
// this is supposed to support the option while hiding it in the settings
int monitorMountedFilesystems = -1;
Expand All @@ -148,7 +210,8 @@ void SettingsDialog::saveSettings() {
ui->applicationsDirLineEdit->text(),
ui->daemonIsEnabledCheckBox->isChecked(),
additionalDirsToWatch,
monitorMountedFilesystems);
monitorMountedFilesystems,
excludePaths);

// reload settings
loadSettings();
Expand Down Expand Up @@ -226,3 +289,66 @@ void SettingsDialog::onDirectoryToWatchItemActivated(QListWidgetItem* item) {
// we activate the button based on whether there's an item selected
ui->additionalDirsRemoveButton->setEnabled(item != nullptr);
}

void SettingsDialog::onAddExcludeDirButtonClicked() {
QFileDialog fileDialog(this);

fileDialog.setFileMode(QFileDialog::DirectoryOnly);
fileDialog.setWindowTitle(tr("Select directories to exclude"));
fileDialog.setDirectory(QStandardPaths::locate(QStandardPaths::HomeLocation, ".", QStandardPaths::LocateDirectory));

// Gtk+ >= 3 segfaults when trying to use the native dialog, therefore we need to enforce the Qt one
// See #218 for more information
fileDialog.setOption(QFileDialog::DontUseNativeDialog, true);


if (fileDialog.exec()) {
for (const auto& file : fileDialog.selectedFiles()) {
addExcludeToListView(file.endsWith('/') ? file : file + '/');
}
}
}

void SettingsDialog::onAddExcludeFileButtonClicked() {
QFileDialog fileDialog(this);

fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setWindowTitle(tr("Select files to exclude"));
fileDialog.setMimeTypeFilters({"application/vnd.appimage"});
fileDialog.setDirectory(QStandardPaths::locate(QStandardPaths::HomeLocation, ".", QStandardPaths::LocateDirectory));

// Gtk+ >= 3 segfaults when trying to use the native dialog, therefore we need to enforce the Qt one
// See #218 for more information
fileDialog.setOption(QFileDialog::DontUseNativeDialog, true);

if (fileDialog.exec()) {
for (const auto& file : fileDialog.selectedFiles()) {
addExcludeToListView(file);
}
}
}

void SettingsDialog::onRemoveExcludeButtonClicked() {
auto* widget = ui->excludeListWidget;

auto* currentItem = widget->currentItem();

if (currentItem == nullptr)
return;

const auto index = widget->row(currentItem);

// after taking it, we have to delete it ourselves, Qt docs say
auto deletedItem = widget->takeItem(index);
delete deletedItem;

// we should deactivate the remove button once the last item is gone
if (widget->item(0) == nullptr) {
ui->excludeRemoveButton->setEnabled(false);
}
}

void SettingsDialog::onExcludeItemActivated(QListWidgetItem* item) {
// we activate the button based on whether there's an item selected
ui->excludeRemoveButton->setEnabled(item != nullptr);
}
Loading