diff --git a/applications/applications.pro b/applications/applications.pro index 9da4e3696..30aa36b31 100644 --- a/applications/applications.pro +++ b/applications/applications.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs SUBDIRS = \ desktop-file-edit \ + desktop-file-install \ desktop-file-validate \ gio \ launcher \ @@ -36,6 +37,7 @@ xdg-open.depends = system-service gio.depends = system-service xdg-settings.depends = system-service xdg-icon-resource.depends = system-service -desktop-file-edit.depends = +desktop-file-edit.depends = desktop-file-edit +desktop-file-install.depends = INSTALLS += $$SUBDIRS diff --git a/applications/desktop-file-edit/desktop-file-edit.pro b/applications/desktop-file-edit/desktop-file-edit.pro index 894e6b530..e7b171de0 100644 --- a/applications/desktop-file-edit/desktop-file-edit.pro +++ b/applications/desktop-file-edit/desktop-file-edit.pro @@ -8,9 +8,11 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + edit.cpp \ main.cpp -HEADERS += +HEADERS += \ + edit.h TARGET = desktop-file-edit include(../../qmake/common.pri) diff --git a/applications/desktop-file-edit/edit.cpp b/applications/desktop-file-edit/edit.cpp new file mode 100644 index 000000000..b2837800a --- /dev/null +++ b/applications/desktop-file-edit/edit.cpp @@ -0,0 +1,165 @@ +#include "edit.h" + +QCommandLineOption setKeyOption( + "set-key", + "Set the KEY key to the value passed to the next --set-value option. A matching --set-value option is mandatory.", + "KEY" +); +QCommandLineOption setValueOption( + "set-value", + "Set the key specified with the previous --set-key option to VALUE. A matching --set-key option is mandatory.", + "VALUE" +); +QCommandLineOption setNameOption( + "set-name", + "NOT IMPLEMENTED", + "NAME" +); +QCommandLineOption copyNameToGenericNameOption( + "copy-name-to-generic-name", + "Copy the value of the Name key to the GenericName key. Note that a desktop file requires a Name key to be valid, so this option will always have an effect." +); +QCommandLineOption setGenericNameOption( + "set-generic-name", + "Set the generic name (key DisplayName) to GENERIC-NAME. If a generic name was already set, it will be overridden. Localizations of the old generic name will be removed.", + "GENERIC-NAME" +); +QCommandLineOption copyGenericNameToNameOption( + "copy-generic-name-to-name", + "NOT IMPLEMENTED" +); +QCommandLineOption setCommentOption( + "set-comment", + "NOT IMPLEMENTED", + "COMMENT" +); +QCommandLineOption setIconOption( + "set-icon", + "Set the icon (key Icon) to ICON. If an icon was already set, it will be overridden. Localizations of the old icon will be removed.", + "ICON" +); +QCommandLineOption addCategoryOption( + "add-category", + "NOT IMPLEMENTED", + "CATEGORY" +); +QCommandLineOption removeCategoryOption( + "remove-category", + "NOT IMPLEMENTED", + "CATEGORY" +); +QCommandLineOption addMimeTypeOption( + "add-mime-type", + "NOT IMPLEMENTED", + "MIME-TYPE" +); +QCommandLineOption removeMimeTypeOption( + "remove-mime-type", + "NOT IMPLEMENTED", + "MIME-TYPE" +); +QCommandLineOption addOnlyShowInOption( + "add-only-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption removeOnlyShowInOption( + "remove-only-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption addNotShowInOption( + "add-not-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption removeNotShowInOption( + "remove-not-show-in", + "NOT IMPLEMENTED", + "ENVIRONMENT" +); +QCommandLineOption removeKeyOption( + "remove-key", + "Remove the KEY key from the desktop files, if present.", + "KEY" +); + +void addEditOptions(QCommandLineParser& parser){ + parser.addOption(setKeyOption); + parser.addOption(setValueOption); + parser.addOption(setNameOption); + parser.addOption(copyNameToGenericNameOption); + parser.addOption(setGenericNameOption); + parser.addOption(copyGenericNameToNameOption); + parser.addOption(setCommentOption); + parser.addOption(setIconOption); + parser.addOption(addCategoryOption); + parser.addOption(removeCategoryOption); + parser.addOption(addMimeTypeOption); + parser.addOption(removeMimeTypeOption); + parser.addOption(addOnlyShowInOption); + parser.addOption(removeOnlyShowInOption); + parser.addOption(addNotShowInOption); + parser.addOption(removeNotShowInOption); + parser.addOption(removeKeyOption); +} + +bool validateSetKeyValueOptions(QCommandLineParser& parser){ + auto options = parser.optionNames(); + if(parser.isSet(setKeyOption) || parser.isSet(setValueOption)){ + for(int i=0; i< options.length(); i++){ + auto option = options[i]; + if(setValueOption.names().contains(option)){ + qDebug() << "Option \"--set-value\" used without a prior \"--set-key\" option"; + qDebug() << "Run 'desktop-file-edit --help' to see a full list of available command line options."; + return false; + } + if(setKeyOption.names().contains(option)){ + option = options[++i]; + if(!setValueOption.names().contains(option)){ + qDebug() << "Option \"--set-key\" used without a following \"--set-value\" option"; + qDebug() << "Run 'desktop-file-edit --help' to see a full list of available command line options."; + return false; + } + } + } + } + return true; +} + +void applyChanges(QCommandLineParser& parser, QJsonObject reg, QString name){ + int removeIndex = 0; + int keyIndex = 0; + int genericNameIndex = 0; + int iconIndex = 0; + QString key; + auto options = parser.optionNames(); + for(int i=0; i< options.length(); i++){ + auto option = options[i]; + if(copyNameToGenericNameOption.names().contains(option)){ + reg["displayName"] = name; + continue; + } + if(removeKeyOption.names().contains(option)){ + reg.remove(parser.values(removeKeyOption)[removeIndex]); + removeIndex++; + continue; + } + if(setGenericNameOption.names().contains(option)){ + reg["displayname"] = parser.values(setGenericNameOption)[genericNameIndex]; + genericNameIndex++; + continue; + } + if(setIconOption.names().contains(option)){ + reg["icon"] = parser.values(setIconOption)[iconIndex]; + iconIndex++; + continue; + } + if(setKeyOption.names().contains(option)){ + key = parser.values(setKeyOption)[keyIndex]; + reg[key] = parser.values(setValueOption)[keyIndex]; + i++; + keyIndex++; + } + } +} diff --git a/applications/desktop-file-edit/edit.h b/applications/desktop-file-edit/edit.h new file mode 100644 index 000000000..d5ec4cc83 --- /dev/null +++ b/applications/desktop-file-edit/edit.h @@ -0,0 +1,12 @@ +#pragma once +#ifndef EDIT_H +#define EDIT_H + +#include +#include + +void addEditOptions(QCommandLineParser& parser); +bool validateSetKeyValueOptions(QCommandLineParser& parser); +void applyChanges(QCommandLineParser& parser, QJsonObject reg, QString name); + +#endif // EDIT_H diff --git a/applications/desktop-file-edit/main.cpp b/applications/desktop-file-edit/main.cpp index 60e0d46b8..5f62980e8 100644 --- a/applications/desktop-file-edit/main.cpp +++ b/applications/desktop-file-edit/main.cpp @@ -6,6 +6,8 @@ #include #include +#include "edit.h" + using namespace Oxide::Sentry; using namespace Oxide::JSON; using namespace Oxide::Applications; @@ -27,132 +29,15 @@ int main(int argc, char *argv[]){ parser.applicationDescription(); parser.addHelpOption(); parser.addVersionOption(); - - QCommandLineOption setKeyOption( - "set-key", - "Set the KEY key to the value passed to the next --set-value option. A matching --set-value option is mandatory.", - "KEY" - ); - parser.addOption(setKeyOption); - QCommandLineOption setValueOption( - "set-value", - "Set the key specified with the previous --set-key option to VALUE. A matching --set-key option is mandatory.", - "VALUE" - ); - parser.addOption(setValueOption); - QCommandLineOption setNameOption( - "set-name", - "NOT IMPLEMENTED", - "NAME" - ); - parser.addOption(setNameOption); - QCommandLineOption copyNameToGenericNameOption( - "copy-name-to-generic-name", - "Copy the value of the Name key to the GenericName key. Note that a desktop file requires a Name key to be valid, so this option will always have an effect." - ); - parser.addOption(copyNameToGenericNameOption); - QCommandLineOption setGenericNameOption( - "set-generic-name", - "Set the generic name (key DisplayName) to GENERIC-NAME. If a generic name was already set, it will be overridden. Localizations of the old generic name will be removed.", - "GENERIC-NAME" - ); - parser.addOption(setGenericNameOption); - QCommandLineOption copyGenericNameToNameOption( - "copy-generic-name-to-name", - "NOT IMPLEMENTED" - ); - parser.addOption(copyGenericNameToNameOption); - QCommandLineOption setCommentOption( - "set-comment", - "NOT IMPLEMENTED", - "COMMENT" - ); - parser.addOption(setCommentOption); - QCommandLineOption setIconOption( - "set-icon", - "Set the icon (key Icon) to ICON. If an icon was already set, it will be overridden. Localizations of the old icon will be removed.", - "ICON" - ); - parser.addOption(setIconOption); - QCommandLineOption addCategoryOption( - "add-category", - "NOT IMPLEMENTED", - "CATEGORY" - ); - parser.addOption(addCategoryOption); - QCommandLineOption removeCategoryOption( - "remove-category", - "NOT IMPLEMENTED", - "CATEGORY" - ); - parser.addOption(removeCategoryOption); - QCommandLineOption addMimeTypeOption( - "add-mime-type", - "NOT IMPLEMENTED", - "MIME-TYPE" - ); - parser.addOption(addMimeTypeOption); - QCommandLineOption removeMimeTypeOption( - "remove-mime-type", - "NOT IMPLEMENTED", - "MIME-TYPE" - ); - parser.addOption(removeMimeTypeOption); - QCommandLineOption addOnlyShowInOption( - "add-only-show-in", - "NOT IMPLEMENTED", - "ENVIRONMENT" - ); - parser.addOption(addOnlyShowInOption); - QCommandLineOption removeOnlyShowInOption( - "remove-only-show-in", - "NOT IMPLEMENTED", - "ENVIRONMENT" - ); - parser.addOption(removeOnlyShowInOption); - QCommandLineOption addNotShowInOption( - "add-not-show-in", - "NOT IMPLEMENTED", - "ENVIRONMENT" - ); - parser.addOption(addNotShowInOption); - QCommandLineOption removeNotShowInOption( - "remove-not-show-in", - "NOT IMPLEMENTED", - "ENVIRONMENT" - ); - parser.addOption(removeNotShowInOption); - QCommandLineOption removeKeyOption( - "remove-key", - "Remove the KEY key from the desktop files, if present.", - "KEY" - ); - parser.addOption(removeKeyOption); - + addEditOptions(parser); parser.addPositionalArgument("FILE", "Application registration to edit"); parser.process(app); auto args = parser.positionalArguments(); if(args.empty() || args.length() > 1){ parser.showHelp(EXIT_FAILURE); } - auto options = parser.optionNames(); - if(parser.isSet(setKeyOption) || parser.isSet(setValueOption)){ - for(int i=0; i< options.length(); i++){ - auto option = options[i]; - if(setValueOption.names().contains(option)){ - qDebug() << "Option \"--set-value\" used without a prior \"--set-key\" option"; - qDebug() << "Run 'desktop-file-edit --help' to see a full list of available command line options."; - return EXIT_FAILURE; - } - if(setKeyOption.names().contains(option)){ - option = options[++i]; - if(!setValueOption.names().contains(option)){ - qDebug() << "Option \"--set-key\" used without a following \"--set-value\" option"; - qDebug() << "Run 'desktop-file-edit --help' to see a full list of available command line options."; - return EXIT_FAILURE; - } - } - } + if(!validateSetKeyValueOptions(parser)){ + return EXIT_FAILURE; } auto path = args.first(); QFile file(path); @@ -173,40 +58,7 @@ int main(int argc, char *argv[]){ QFileInfo info(file); auto name = info.baseName(); - int removeIndex = 0; - int keyIndex = 0; - int genericNameIndex = 0; - int iconIndex = 0; - QString key; - for(int i=0; i< options.length(); i++){ - auto option = options[i]; - if(copyNameToGenericNameOption.names().contains(option)){ - reg["displayName"] = name; - continue; - } - if(removeKeyOption.names().contains(option)){ - reg.remove(parser.values(removeKeyOption)[removeIndex]); - removeIndex++; - continue; - } - if(setGenericNameOption.names().contains(option)){ - reg["displayname"] = parser.values(setGenericNameOption)[genericNameIndex]; - genericNameIndex++; - continue; - } - if(setIconOption.names().contains(option)){ - reg["icon"] = parser.values(setIconOption)[iconIndex]; - iconIndex++; - continue; - } - if(setKeyOption.names().contains(option)){ - key = parser.values(setKeyOption)[keyIndex]; - reg[key] = parser.values(setValueOption)[keyIndex]; - i++; - keyIndex++; - } - } - + applyChanges(parser, reg, name); auto json = toJson(reg, QJsonDocument::Indented); file.write(json.toUtf8()); file.close(); diff --git a/applications/desktop-file-install/.gitignore b/applications/desktop-file-install/.gitignore new file mode 100644 index 000000000..fab7372d7 --- /dev/null +++ b/applications/desktop-file-install/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/applications/desktop-file-install/desktop-file-install.pro b/applications/desktop-file-install/desktop-file-install.pro new file mode 100644 index 000000000..d41b248a1 --- /dev/null +++ b/applications/desktop-file-install/desktop-file-install.pro @@ -0,0 +1,23 @@ +QT -= gui +QT += dbus + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += QT_DEPRECATED_WARNINGS +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + ../desktop-file-edit/edit.cpp \ + main.cpp + +HEADERS += \ + ../desktop-file-edit/edit.h + +TARGET = desktop-file-install +include(../../qmake/common.pri) +target.path = /opt/bin +INSTALLS += target + +include(../../qmake/liboxide.pri) +include(../../qmake/sentry.pri) diff --git a/applications/desktop-file-install/main.cpp b/applications/desktop-file-install/main.cpp new file mode 100644 index 000000000..1f36a9c19 --- /dev/null +++ b/applications/desktop-file-install/main.cpp @@ -0,0 +1,121 @@ +#include +#include +#include + +#include +#include +#include + +#include "../desktop-file-edit/edit.h" + +using namespace Oxide::Sentry; +using namespace Oxide::JSON; +using namespace Oxide::Applications; + +QTextStream& qStdOut(){ + static QTextStream ts( stdout ); + return ts; +} + +int main(int argc, char *argv[]){ + QCoreApplication app(argc, argv); + sentry_init("desktop-file-install", argv); + app.setOrganizationName("Eeems"); + app.setOrganizationDomain(OXIDE_SERVICE); + app.setApplicationName("desktop-file-install"); + app.setApplicationVersion(APP_VERSION); + QCommandLineParser parser; + parser.setApplicationDescription("Install application registration files"); + parser.applicationDescription(); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption dirOption( + "dir", + "Install desktop files to the DIR directory.", + "DIR", + OXIDE_APPLICATION_REGISTRATIONS_DIRECTORY + ); + parser.addOption(dirOption); + QCommandLineOption modeOption( + {"m", "mode"}, + "NOT IMPLEMENTED", + "MODE" + ); + parser.addOption(modeOption); + QCommandLineOption vendorOption( + "vendor", + "NOT IMPLEMENTED", + "VENDOR" + ); + parser.addOption(vendorOption); + QCommandLineOption deleteOriginalOption( + "delete-original", + "Delete the source registration files, leaving only the target files. Effectively \"renames\" the registration files." + ); + parser.addOption(deleteOriginalOption); + QCommandLineOption rebuildMimeInfoCacheOption( + "rebuild-mime-info-cache", + "NOT IMPLEMENTED" + ); + parser.addOption(rebuildMimeInfoCacheOption); + addEditOptions(parser); + parser.addPositionalArgument("FILE", "Application registration(s) to install", "FILE..."); + parser.process(app); + auto args = parser.positionalArguments(); + if(args.empty()){ + parser.showHelp(EXIT_FAILURE); + } + if(!validateSetKeyValueOptions(parser)){ + return EXIT_FAILURE; + } + bool success = true; + for(auto path : args){ + QFile file(path); + if(!file.exists()){ + qDebug() << "Error on file" << path << ": No such file or directory"; + success = false; + continue; + } + auto reg = getRegistration(&file); + file.close(); + QFileInfo info(file); + QFile toFile(QDir::cleanPath(parser.value(dirOption) + QDir::separator() + info.baseName() + ".oxide")); + if(!toFile.open(QFile::WriteOnly | QFile::Truncate)){ + qDebug() << "Error on file" << path << ": Cannot write to file"; + success = false; + continue; + } + if(info.suffix() != "oxide"){ + qDebug() << path.toStdString().c_str() << ": error: filename does not have a .oxide extension"; + success = false; + continue; + } + if(reg.isEmpty()){ + qDebug() << "Error on file" << path << ": is not valid"; + success = false; + continue; + } + + auto name = info.baseName(); + applyChanges(parser, reg, name); + bool hadError = false; + for(auto error : validateRegistration(name, reg)){ + qStdOut() << path << ": " << error << Qt::endl; + auto level = error.level; + if(level == ErrorLevel::Error || level == ErrorLevel::Critical){ + hadError = true; + } + } + if(hadError){ + success = false; + continue; + } + auto json = toJson(reg, QJsonDocument::Indented); + toFile.write(json.toUtf8()); + toFile.close(); + if(parser.isSet(deleteOriginalOption)){ + file.remove(); + } + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +}