diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a3b7bb..8917490 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.19) -set(HYPRCURSOR_VERSION "0.1.7") +set(HYPRCURSOR_VERSION "0.1.9") add_compile_definitions(HYPRCURSOR_VERSION="${HYPRCURSOR_VERSION}") project(hyprcursor diff --git a/README.md b/README.md index 85dbf8b..88a8d57 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Util: ### Build ```sh cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build -cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` +cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF` ``` Install with: diff --git a/flake.lock b/flake.lock index 599b845..9468df0 100644 --- a/flake.lock +++ b/flake.lock @@ -5,14 +5,16 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems" + "systems": [ + "systems" + ] }, "locked": { - "lastModified": 1709914708, - "narHash": "sha256-bR4o3mynoTa1Wi4ZTjbnsZ6iqVcPGriXp56bZh5UFTk=", + "lastModified": 1713121246, + "narHash": "sha256-502X0Q0fhN6tJK7iEUA8CghONKSatW/Mqj4Wappd++0=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "a685493fdbeec01ca8ccdf1f3655c044a8ce2fe2", + "rev": "78fcaa27ae9e1d782faa3ff06c8ea55ddce63706", "type": "github" }, "original": { @@ -23,11 +25,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708475490, - "narHash": "sha256-g1v0TsWBQPX97ziznfJdWhgMyMGtoBFs102xSYO4syU=", + "lastModified": 1712963716, + "narHash": "sha256-WKm9CvgCldeIVvRz87iOMi8CFVB1apJlkUT4GGvA0iM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "0e74ca98a74bc7270d28838369593635a5db3260", + "rev": "cfd6b5fc90b15709b780a5a1619695a88505a176", "type": "github" }, "original": { @@ -41,7 +43,7 @@ "inputs": { "hyprlang": "hyprlang", "nixpkgs": "nixpkgs", - "systems": "systems_2" + "systems": "systems" } }, "systems": { @@ -58,21 +60,6 @@ "repo": "default-linux", "type": "github" } - }, - "systems_2": { - "locked": { - "lastModified": 1689347949, - "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", - "owner": "nix-systems", - "repo": "default-linux", - "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default-linux", - "type": "github" - } } }, "root": "root", diff --git a/hyprcursor-util/src/main.cpp b/hyprcursor-util/src/main.cpp index 8602d6b..b6235cb 100644 --- a/hyprcursor-util/src/main.cpp +++ b/hyprcursor-util/src/main.cpp @@ -5,11 +5,16 @@ #include #include #include +#include #include #include "internalSharedTypes.hpp" #include "manifest.hpp" #include "meta.hpp" +#ifndef ZIP_LENGTH_TO_END +#define ZIP_LENGTH_TO_END -1 +#endif + enum eOperation { OPERATION_CREATE = 0, OPERATION_EXTRACT = 1, @@ -87,7 +92,7 @@ static std::optional createCursorThemeFromPath(const std::string& p const std::string THEMENAME = manifest.parsedData.name; - std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/') + 1) : out_) + "/theme_" + THEMENAME + "/"; + std::string out = (out_.empty() ? path.substr(0, path.find_last_of('/')) : out_) + "/theme_" + THEMENAME; const std::string CURSORSSUBDIR = manifest.parsedData.cursorsDirectory; const std::string CURSORDIR = path + "/" + CURSORSSUBDIR; @@ -98,6 +103,9 @@ static std::optional createCursorThemeFromPath(const std::string& p // iterate over the directory and record all cursors for (auto& dir : std::filesystem::directory_iterator(CURSORDIR)) { + if (!std::regex_match(dir.path().stem().string(), std::regex("^[A-Za-z0-9_\\-\\.]+$"))) + return "Invalid cursor directory name at " + dir.path().string() + " : characters must be within [A-Za-z0-9_\\-\\.]"; + const auto METAPATH = dir.path().string() + "/meta"; auto& SHAPE = currentTheme.shapes.emplace_back(std::make_unique()); @@ -113,6 +121,8 @@ static std::optional createCursorThemeFromPath(const std::string& p SHAPE->images.push_back(SCursorImage{i.file, i.size, i.delayMs}); } + SHAPE->overrides = meta.parsedData.overrides; + // check if we have at least one image. for (auto& i : SHAPE->images) { @@ -178,7 +188,7 @@ static std::optional createCursorThemeFromPath(const std::string& p // add meta.hl const auto METADIR = std::filesystem::exists(CURRENTCURSORSDIR + "/meta.hl") ? (CURRENTCURSORSDIR + "/meta.hl") : (CURRENTCURSORSDIR + "/meta.toml"); - zip_source_t* meta = zip_source_file(zip, METADIR.c_str(), 0, 0); + zip_source_t* meta = zip_source_file(zip, METADIR.c_str(), 0, ZIP_LENGTH_TO_END); if (!meta) return "(1) failed to add meta " + METADIR + " to hlc"; if (zip_file_add(zip, (std::string{"meta."} + (METADIR.ends_with(".hl") ? "hl" : "toml")).c_str(), meta, ZIP_FL_ENC_UTF_8) < 0) @@ -186,9 +196,9 @@ static std::optional createCursorThemeFromPath(const std::string& p meta = nullptr; - // add each cursor png + // add each cursor image for (auto& i : shape->images) { - zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, 0); + zip_source_t* image = zip_source_file(zip, (CURRENTCURSORSDIR + "/" + i.filename).c_str(), 0, ZIP_LENGTH_TO_END); if (!image) return "(1) failed to add image " + (CURRENTCURSORSDIR + "/" + i.filename) + " to hlc"; if (zip_file_add(zip, (i.filename).c_str(), image, ZIP_FL_ENC_UTF_8) < 0) @@ -199,9 +209,8 @@ static std::optional createCursorThemeFromPath(const std::string& p // close zip and write if (zip_close(zip) < 0) { - zip_error_t ziperror; - zip_error_init_with_code(&ziperror, errp); - return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(&ziperror); + zip_error_t* ziperror = zip_get_error(zip); + return "Failed to write " + OUTPUTFILE + ": " + zip_error_strerror(ziperror); } std::cout << "Written " << OUTPUTFILE << "\n"; @@ -449,4 +458,4 @@ int main(int argc, char** argv, char** envp) { } return 0; -} \ No newline at end of file +} diff --git a/include/hyprcursor/hyprcursor.hpp b/include/hyprcursor/hyprcursor.hpp index 0e0f280..2be1bf3 100644 --- a/include/hyprcursor/hyprcursor.hpp +++ b/include/hyprcursor/hyprcursor.hpp @@ -47,6 +47,22 @@ namespace Hyprcursor { eHyprcursorDataType type = HC_DATA_PNG; }; + /*! + struct for cursor manager options + */ + struct SManagerOptions { + explicit SManagerOptions(); + + /*! + The function used for logging by the cursor manager + */ + PHYPRCURSORLOGFUNC logFn; + /*! + Allow fallback to env and first theme found + */ + bool allowDefaultFallback; + }; + /*! Basic Hyprcursor manager. @@ -58,6 +74,8 @@ namespace Hyprcursor { If none found, bool valid() will be false. If loading fails, bool valid() will be false. + + If theme has no valid cursor shapes, bool valid() will be false. */ class CHyprcursorManager { public: @@ -66,6 +84,7 @@ namespace Hyprcursor { \since 0.1.6 */ CHyprcursorManager(const char* themeName, PHYPRCURSORLOGFUNC fn); + CHyprcursorManager(const char* themeName, SManagerOptions options); ~CHyprcursorManager(); /*! @@ -170,9 +189,10 @@ namespace Hyprcursor { private: void init(const char* themeName_); - CHyprcursorImplementation* impl = nullptr; - bool finalizedAndValid = false; - PHYPRCURSORLOGFUNC logFn = nullptr; + CHyprcursorImplementation* impl = nullptr; + bool finalizedAndValid = false; + bool allowDefaultFallback = true; + PHYPRCURSORLOGFUNC logFn = nullptr; friend class CHyprcursorImplementation; }; diff --git a/libhyprcursor/hyprcursor.cpp b/libhyprcursor/hyprcursor.cpp index a78d2ba..b6793ec 100644 --- a/libhyprcursor/hyprcursor.cpp +++ b/libhyprcursor/hyprcursor.cpp @@ -108,7 +108,7 @@ static std::string getFirstTheme(PHYPRCURSORLOGFUNC logfn) { return ""; } -static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn) { +static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORLOGFUNC logfn, bool allowDefaultFallback) { const auto HOMEENV = getenv("HOME"); if (!HOMEENV) return ""; @@ -134,7 +134,7 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL const auto MANIFESTPATH = themeDir.path().string() + "/manifest"; - if (name.empty()) { + if (allowDefaultFallback && name.empty()) { if (std::filesystem::exists(MANIFESTPATH + ".hl") || std::filesystem::exists(MANIFESTPATH + ".toml")) { Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: found {}", themeDir.path().string()); return std::filesystem::canonical(themeDir.path()).string(); @@ -193,14 +193,19 @@ static std::string getFullPathForThemeName(const std::string& name, PHYPRCURSORL } } - if (!name.empty()) { // try without name + if (allowDefaultFallback && !name.empty()) { // try without name Debug::log(HC_LOG_INFO, logfn, "getFullPathForThemeName: failed, trying without name of {}", name); - return getFullPathForThemeName("", logfn); + return getFullPathForThemeName("", logfn, allowDefaultFallback); } return ""; } +SManagerOptions::SManagerOptions() { + logFn = nullptr; + allowDefaultFallback = true; +} + CHyprcursorManager::CHyprcursorManager(const char* themeName_) { init(themeName_); } @@ -210,16 +215,22 @@ CHyprcursorManager::CHyprcursorManager(const char* themeName_, PHYPRCURSORLOGFUN init(themeName_); } +CHyprcursorManager::CHyprcursorManager(const char* themeName_, SManagerOptions options) { + logFn = options.logFn; + allowDefaultFallback = options.allowDefaultFallback; + init(themeName_); +} + void CHyprcursorManager::init(const char* themeName_) { std::string themeName = themeName_ ? themeName_ : ""; - if (themeName.empty()) { + if (allowDefaultFallback && themeName.empty()) { // try reading from env Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find theme from env"); themeName = themeNameFromEnv(logFn); } - if (themeName.empty()) { + if (allowDefaultFallback && themeName.empty()) { // try finding first, in the hierarchy Debug::log(HC_LOG_INFO, logFn, "CHyprcursorManager: attempting to find any theme"); themeName = getFirstTheme(logFn); @@ -234,7 +245,7 @@ void CHyprcursorManager::init(const char* themeName_) { // initialize theme impl = new CHyprcursorImplementation(this, logFn); impl->themeName = themeName; - impl->themeFullDir = getFullPathForThemeName(themeName, logFn); + impl->themeFullDir = getFullPathForThemeName(themeName, logFn, allowDefaultFallback); if (impl->themeFullDir.empty()) return; @@ -248,6 +259,11 @@ void CHyprcursorManager::init(const char* themeName_) { return; } + if (impl->theme.shapes.empty()) { + Debug::log(HC_LOG_ERR, logFn, "Theme {} has no valid cursor shapes\n", impl->themeName); + return; + } + finalizedAndValid = true; } @@ -301,7 +317,7 @@ SCursorImageData** CHyprcursorManager::getShapesC(int& outSize, const char* shap // find nearest int leader = 13371337; for (auto& image : impl->loadedShapes[shape.get()].images) { - if (std::abs((int)(image->side - info.size)) > leader) + if (std::abs((int)(image->side - info.size)) > std::abs((int)(leader - info.size))) continue; leader = image->side; diff --git a/libhyprcursor/meta.cpp b/libhyprcursor/meta.cpp index 77e554e..c7963d4 100644 --- a/libhyprcursor/meta.cpp +++ b/libhyprcursor/meta.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "VarList.hpp" @@ -95,6 +96,11 @@ static Hyprlang::CParseResult parseDefineSize(const char* C, const char* V) { RHS = LL; } + if (!std::regex_match(RHS, std::regex("^[A-Za-z0-9_\\-\\.]+$"))) { + result.setError("Invalid cursor file name, characters must be within [A-Za-z0-9_\\-\\.] (if this seems like a mistake, check for invisible characters)"); + return result; + } + size.file = RHS; if (!size.file.ends_with(".svg")) { @@ -132,7 +138,9 @@ std::optional CMeta::parseHL() { meta->registerHandler(::parseDefineSize, "define_size", {.allowFlags = false}); meta->registerHandler(::parseOverride, "define_override", {.allowFlags = false}); meta->commence(); - meta->parse(); + const auto RESULT = meta->parse(); + if (RESULT.error) + return RESULT.getError(); } catch (const char* err) { return "failed parsing meta: " + std::string{err}; } parsedData.hotspotX = std::any_cast(meta->getConfigValue("hotspot_x"));