diff --git a/data/strings/en.ini b/data/strings/en.ini index 3b3735720..2d70548d3 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -90,6 +90,7 @@ job_working = {0}< {2}%\n<< UI Scaling: {3}% -> {4}%\n<extensions().getCompressedExtensionInfo(filename); + // Check if the filename corresponds to aseprite-default theme + if (base::string_to_lower(info.name) == + Extension::kAsepriteDefaultThemeExtensionName) { + ui::Alert::show(Strings::alerts_cannot_install_default_extension()); + return false; + } + // Install? if (ui::Alert::show(Strings::alerts_install_extension(filename)) != 1) return false; @@ -1378,7 +1389,8 @@ private: if (!ext->isEnabled()) continue; - if (ext->themes().empty()) + if (ext->themes().empty() || + isExtensionADuplicatedDefaultTheme(ext)) continue; if (first) { @@ -1410,6 +1422,9 @@ private: extensionsList()->addChild(sep); for (auto e : App::instance()->extensions()) { if (e->category() == category) { + if (category == Extension::Category::Themes && + isExtensionADuplicatedDefaultTheme(e)) + continue; ExtensionItem* item = new ExtensionItem(e); extensionsList()->addChild(item); hasItems = true; @@ -1571,6 +1586,10 @@ private: // package.json file. ExtensionInfo info = exts.getCompressedExtensionInfo(filename); + if (info.defaultTheme) { + ui::Alert::show(Strings::alerts_cannot_install_default_extension()); + return; + } // Check if the extension already exist for (auto ext : exts) { if (base::string_to_lower(ext->name()) != @@ -1749,6 +1768,17 @@ private: return paths; } + static base::paths getUserDirPaths(const base::paths& dirNames) { + ResourceFinder rf; + for (auto& fn : dirNames) + rf.includeUserDir(fn.c_str()); + + base::paths paths; + while (rf.next()) + paths.push_back(base::normalize_path(rf.filename())); + return paths; + } + void updateCategoryVisibility() { bool visibleCategories[int(Extension::Category::Max)]; for (auto& v : visibleCategories) @@ -1764,6 +1794,20 @@ private: } } + // Function to determine if the input extension is the default theme + static bool isExtensionADuplicatedDefaultTheme(const Extension* e) { + if (!e->isDefaultTheme()) + return false; + auto userThemePaths = + getUserDirPaths({"extensions", skin::SkinTheme::kThemesFolderName}); + for (auto& p : userThemePaths) { + // Has the user path (p) the same path of the extension (e->path())? + if (std::strncmp(e->path().c_str(), p.c_str(), p.size()) == 0) + return true; + } + return false; + } + #ifdef LAF_WINDOWS void onTabletAPIChange() { const bool pointerApi = tabletApiWindowsPointer()->isSelected(); diff --git a/src/app/extensions.cpp b/src/app/extensions.cpp index 768a9435e..6b6a1d5b2 100644 --- a/src/app/extensions.cpp +++ b/src/app/extensions.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2023 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -51,12 +51,14 @@ namespace app { +const char* Extension::kAsepriteDefaultThemeExtensionName = "aseprite-theme"; +const char* Extension::kAsepriteDefaultThemeId = "default"; + namespace { const char* kPackageJson = "package.json"; const char* kInfoJson = "__info.json"; const char* kPrefLua = "__pref.lua"; -const char* kAsepriteDefaultThemeExtensionName = "aseprite-theme"; class ReadArchive { public: @@ -283,6 +285,8 @@ void Extension::addTheme(const std::string& id, const std::string& path, const std::string& variant) { + if (id == kAsepriteDefaultThemeId && !isDefaultTheme()) + return; m_themes[id] = ThemeInfo(path, variant); updateCategory(Category::Themes); } @@ -1003,6 +1007,21 @@ ExtensionInfo Extensions::getCompressedExtensionInfo(const std::string& zipFn) std::string err; auto json = json11::Json::parse(out.str(), err); if (err.empty()) { + if (json["contributes"].is_object()) { + auto themes = json["contributes"]["themes"]; + if (json["name"].string_value() == Extension::kAsepriteDefaultThemeExtensionName) + info.defaultTheme = true; + else { + if (themes.is_array()) { + for (int i = 0; i < themes.array_items().size(); i++) { + if (themes[i]["id"].string_value() == Extension::kAsepriteDefaultThemeId) { + info.defaultTheme = true; + break; + } + } + } + } + } info.name = json["name"].string_value(); info.version = json["version"].string_value(); info.dstPath = base::join_path(m_userExtensionsPath, info.name); diff --git a/src/app/extensions.h b/src/app/extensions.h index 072ecfbd6..152170142 100644 --- a/src/app/extensions.h +++ b/src/app/extensions.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2020-2023 Igara Studio S.A. +// Copyright (C) 2020-2024 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -34,6 +34,7 @@ namespace app { std::string version; std::string dstPath; std::string commonPath; + bool defaultTheme = false; }; enum DeletePluginPref { kNo, kYes }; @@ -41,6 +42,8 @@ namespace app { class Extension { friend class Extensions; public: + static const char* kAsepriteDefaultThemeExtensionName; + static const char* kAsepriteDefaultThemeId; enum class Category { None, @@ -54,6 +57,8 @@ namespace app { Max }; + bool isDefaultTheme() const; + class DitheringMatrixInfo { public: DitheringMatrixInfo(); @@ -150,7 +155,6 @@ namespace app { void uninstall(const DeletePluginPref delPref); void uninstallFiles(const std::string& path, const DeletePluginPref delPref); - bool isDefaultTheme() const; void updateCategory(const Category newCategory); #ifdef ENABLE_SCRIPTING void initScripts();