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

Add support to register AppImage With Additional Actions to the IntegrationManager #138

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 22 additions & 0 deletions include/appimage/desktop_integration/IntegrationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ namespace appimage {
*/
void registerAppImage(const core::AppImage& appImage) const;

/**
* @brief Register an AppImage in the system adding custom desktop entry actions
*
* Extract the application main desktop entry, icons and mime type packages. Modifies their content to
* properly match the AppImage file location and deploy them into the use XDG_DATA_HOME appending a
* prefix to each file. Such prefix is composed as "<vendor id>_<appimage_path_md5>_<old_file_name>"
*
* The desktop entry actions must follow the specification for additional application actions at https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s11.html.
* The map key should be the action identifier and the value the action fields in a plain string i.e.:
*
* ```
* std::map<std::string, std::string> additionalApplicationActions = {{"Remove",
* "[Desktop Action Remove]\n"
* "Name=\"Remove application\"\n"
* "Icon=remove\n"
* "Exec=remove-appimage-helper /path/to/the/AppImage\n"}};
*```
* @param appImage
* @param additionalApplicationActions desktop entry actions to be added.
*/
void registerAppImage(const core::AppImage& appImage, std::map<std::string, std::string> additionalApplicationActions) const;
azubieta marked this conversation as resolved.
Show resolved Hide resolved

/**
* @brief Unregister an AppImage in the system
*
Expand Down
15 changes: 15 additions & 0 deletions src/libappimage/desktop_integration/IntegrationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ namespace appimage {
}
}

void IntegrationManager::registerAppImage(const core::AppImage &appImage,
std::map<std::string, std::string> additionalApplicationActions) const {
try {
integrator::Integrator i(appImage, d->xdgDataHome);
i.setAdditionalApplicationActions(additionalApplicationActions);
i.integrate();
} catch (...) {
// Remove any file created during the integration process
unregisterAppImage(appImage.getPath());

// Rethrow
throw;
}
}

bool IntegrationManager::isARegisteredAppImage(const std::string& appImagePath) const {
// Generate AppImage Id
const auto& appImageId = d->generateAppImageId(appImagePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <XdgUtils/DesktopEntry/DesktopEntryExecValue.h>
#include <XdgUtils/DesktopEntry/DesktopEntryStringsValue.h>
#include <XdgUtils/DesktopEntry/DesktopEntryKeyPath.h>
#include <map>
#include <utility>

// local
#include "DesktopEntryEditor.h"
Expand Down Expand Up @@ -36,6 +38,8 @@ namespace appimage {

appendVersionToName(desktopEntry);

appendApplicationActions(desktopEntry);

// set identifier
desktopEntry.set("Desktop Entry/X-AppImage-Identifier", identifier);
}
Expand Down Expand Up @@ -114,6 +118,10 @@ namespace appimage {
}
}

void DesktopEntryEditor::setAdditionalApplicationActions(std::map<std::string, std::string> additionalApplicationActions) {
DesktopEntryEditor::additionalApplicationActions = std::move(additionalApplicationActions);
}

void DesktopEntryEditor::setExecPaths(XdgUtils::DesktopEntry::DesktopEntry& desktopEntry) {
// Edit "Desktop Entry/Exec"
DesktopEntryExecValue execValue(desktopEntry.get("Desktop Entry/Exec"));
Expand All @@ -133,6 +141,30 @@ namespace appimage {
desktopEntry.set(keyPath, actionExecValue.dump());
}
}

void DesktopEntryEditor::appendApplicationActions(XdgUtils::DesktopEntry::DesktopEntry &entry) {
for (auto itr = additionalApplicationActions.begin(); itr != additionalApplicationActions.end(); ++itr) {
try {
// validate correctness of the action specification
std::stringstream stringstream(itr->second);
XdgUtils::DesktopEntry::DesktopEntry action(stringstream);

// Add action
std::string actionsString = static_cast<std::string>(entry["Desktop Entry/Actions"]);
DesktopEntryStringsValue actions(actionsString);

actions.append(itr->first);
entry.set("Desktop Entry/Actions", actions.dump());

// Add action definition
for (const auto &path: action.paths())
entry[path] = action.get(path);

} catch (const DesktopEntryError &error) {
throw DesktopEntryEditError(std::string("Malformed action: ") + error.what());
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ namespace appimage {
*/
void setIdentifier(const std::string& uuid);

/**
* Set the application actions that must be appended to the desktop entry on edit.
* @param additionalApplicationActions
*/
void setAdditionalApplicationActions(std::map<std::string, std::string> additionalApplicationActions);

/**
* Modifies the Desktop Entry according to the set parameters.
* @param desktopEntry
Expand All @@ -50,6 +56,7 @@ namespace appimage {
std::string vendorPrefix;
std::string appImagePath;
std::string appImageVersion;
std::map<std::string, std::string> additionalApplicationActions;

/**
* Set Exec and TryExec entries in the 'Desktop Entry' and 'Desktop Action' groups pointing to the
Expand All @@ -71,6 +78,22 @@ namespace appimage {
* If none of both options are valid the names will remain unchanged.
*/
void appendVersionToName(XdgUtils::DesktopEntry::DesktopEntry& entry);

/**
* @brief Append the additionalApplicationActions to <entry>
* The desktop entry actions must follow the specification for additional application actions at https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s11.html.
* The map key should be the action identifier and the value the action fields in a plain string i.e.:
*
* std::map<std::string, std::string> applicationActions = {{"Remove",
* "[Desktop Action Remove]\n"
* "Name=\"Remove application\"\n"
* "Icon=remove\n"
* "Exec=remove-appimage-helper /path/to/the/AppImage\n"}};
*
*
* @param entry
*/
void appendApplicationActions(XdgUtils::DesktopEntry::DesktopEntry& entry);
};

}
Expand Down
9 changes: 9 additions & 0 deletions src/libappimage/desktop_integration/integrator/Integrator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace appimage {
core::AppImage appImage;
bf::path xdgDataHome;
std::string appImageId;
std::map<std::string, std::string> additionalApplicationActions;

ResourcesExtractor resourcesExtractor;
DesktopEntry desktopEntry;
Expand Down Expand Up @@ -151,6 +152,10 @@ namespace appimage {
editor.setAppImagePath(appImage.getPath());
// Set the identifier to be used while prefixing the icon files
editor.setIdentifier(md5str);

// Set the additional applications actions to be appended
editor.setAdditionalApplicationActions(additionalApplicationActions);

// Apply changes to the desktop entry
editor.edit(entry);
}
Expand Down Expand Up @@ -302,6 +307,10 @@ namespace appimage {
d->deployMimeTypePackages();
d->setExecutionPermission();
}

void Integrator::setAdditionalApplicationActions(std::map<std::string, std::string> additionalApplicationActions) {
d->additionalApplicationActions = std::move(additionalApplicationActions);
}
}
}
}
2 changes: 2 additions & 0 deletions src/libappimage/desktop_integration/integrator/Integrator.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ namespace appimage {
*/
void integrate();

void setAdditionalApplicationActions(std::map<std::string, std::string> additionalApplicationActions);

private:
class Priv;
std::unique_ptr<Priv> d; // opaque pointer
Expand Down
33 changes: 33 additions & 0 deletions tests/libappimage/desktop_integration/TestIntegrationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// library headers
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
#include <XdgUtils/DesktopEntry/DesktopEntry.h>

// local
#include "appimage/desktop_integration/exceptions.h"
Expand Down Expand Up @@ -52,6 +53,38 @@ TEST_F(TestIntegrationManager, registerAppImage) {
ASSERT_TRUE(bf::exists(expectedIconFilePath));
}

TEST_F(TestIntegrationManager, registerAppImageWithAdditionalActions) {
std::string appImagePath = TEST_DATA_DIR "Echo-x86_64.AppImage";
IntegrationManager manager(userDirPath.string());
appimage::core::AppImage appImage(appImagePath);
std::map<std::string, std::string> applicationActions = {{"Remove",
"[Desktop Action Remove]\n"
"Name=\"Remove application\"\n"
"Name[es]=\"Eliminar aplicación\"\n"
"Icon=remove\n"
"Exec=remove-appimage-helper /path/to/the/AppImage\n"}};

manager.registerAppImage(appImage, applicationActions);

std::string md5 = appimage::utils::hashPath(appImagePath.c_str());

bf::path expectedDesktopFilePath = userDirPath / ("applications/appimagekit_" + md5 + "-Echo.desktop");
ASSERT_TRUE(bf::exists(expectedDesktopFilePath));

bf::path expectedIconFilePath =
userDirPath / ("icons/hicolor/scalable/apps/appimagekit_" + md5 + "_utilities-terminal.svg");
ASSERT_TRUE(bf::exists(expectedIconFilePath));

std::ifstream fin(expectedDesktopFilePath.string());
XdgUtils::DesktopEntry::DesktopEntry entry(fin);

ASSERT_EQ(std::string("Remove;"), entry.get("Desktop Entry/Actions"));
ASSERT_EQ(std::string("\"Remove application\""), entry.get("Desktop Action Remove/Name"));
ASSERT_EQ(std::string("\"Eliminar aplicación\""), entry.get("Desktop Action Remove/Name[es]"));
ASSERT_EQ(std::string("remove"), entry.get("Desktop Action Remove/Icon"));
ASSERT_EQ(std::string("remove-appimage-helper /path/to/the/AppImage"), entry.get("Desktop Action Remove/Exec"));
}

TEST_F(TestIntegrationManager, isARegisteredAppImage) {
std::string appImagePath = TEST_DATA_DIR "Echo-x86_64.AppImage";
IntegrationManager manager(userDirPath.string());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,29 @@ TEST_F(DesktopEntryEditorTests, setIdentifier) {

ASSERT_EQ(entry.get("Desktop Entry/X-AppImage-Identifier"), "uuid");
}

TEST_F(DesktopEntryEditorTests, setAdditionalApplicationActions) {
XdgUtils::DesktopEntry::DesktopEntry entry(originalData);

DesktopEntryEditor editor;
editor.setVendorPrefix("prefix");
editor.setIdentifier("uuid");
editor.setAppImageVersion("0.1.1");
std::map<std::string, std::string> applicationActions = {{"Remove",
"[Desktop Action Remove]\n"
"Name=\"Remove application\"\n"
"Name[es]=\"Eliminar aplicación\"\n"
"Icon=remove\n"
"Exec=remove-appimage-helper /path/to/the/AppImage\n"}};

editor.setAdditionalApplicationActions(applicationActions);
editor.edit(entry);

ASSERT_EQ(entry.get("Desktop Entry/X-AppImage-Identifier"), "uuid");

ASSERT_EQ(std::string("Gallery;Create;Remove;"), entry.get("Desktop Entry/Actions"));
ASSERT_EQ(std::string("\"Remove application\""), entry.get("Desktop Action Remove/Name"));
ASSERT_EQ(std::string("\"Eliminar aplicación\""), entry.get("Desktop Action Remove/Name[es]"));
ASSERT_EQ(std::string("remove"), entry.get("Desktop Action Remove/Icon"));
ASSERT_EQ(std::string("remove-appimage-helper /path/to/the/AppImage"), entry.get("Desktop Action Remove/Exec"));
}
azubieta marked this conversation as resolved.
Show resolved Hide resolved