diff --git a/CMakeLists.txt b/CMakeLists.txt index 29e403100..0b359e340 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -547,6 +547,7 @@ if(LINUX) src/detection/opengl/opengl_linux.c src/detection/os/os_linux.c src/detection/packages/packages_linux.c + src/detection/packages/packages_nix.c src/detection/poweradapter/poweradapter_linux.c src/detection/processes/processes_linux.c src/detection/gtk_qt/qt.c @@ -939,6 +940,7 @@ elseif(APPLE) src/detection/opengl/opengl_apple.c src/detection/os/os_apple.m src/detection/packages/packages_apple.c + src/detection/packages/packages_nix.c src/detection/poweradapter/poweradapter_apple.c src/detection/processes/processes_bsd.c src/detection/sound/sound_apple.c diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h index c824b448a..c71711dcc 100644 --- a/src/detection/packages/packages.h +++ b/src/detection/packages/packages.h @@ -55,6 +55,9 @@ const char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* option bool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result); bool ffPackagesWriteCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, uint32_t num_elements); +#if (defined(__linux__) && !defined(__ANDROID__)) || defined(__APPLE__) +uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname); +#endif #ifndef _WIN32 uint32_t ffPackagesGetNumElements(const char* dirname, bool isdir); #endif diff --git a/src/detection/packages/packages_apple.c b/src/detection/packages/packages_apple.c index 8391a0f56..5a176c834 100644 --- a/src/detection/packages/packages_apple.c +++ b/src/detection/packages/packages_apple.c @@ -4,91 +4,66 @@ #include "common/processing.h" #include "util/stringUtils.h" -static void countBrewPackages(const char* dirname, FFPackagesResult* result) +static void countBrewPackages(FFstrbuf* baseDir, FFPackagesResult* result) { - FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateS(dirname); - uint32_t baseDirLength = baseDir.length; - - ffStrbufAppendS(&baseDir, "/Caskroom"); - result->brewCask += ffPackagesGetNumElements(baseDir.chars, true); - ffStrbufSubstrBefore(&baseDir, baseDirLength); - - ffStrbufAppendS(&baseDir, "/Cellar"); - result->brew += ffPackagesGetNumElements(baseDir.chars, true); - ffStrbufSubstrBefore(&baseDir, baseDirLength); -} - -static void getBrewPackages(FFPackagesResult* result) -{ - const char* prefix = getenv("HOMEBREW_PREFIX"); - if(ffStrSet(prefix)) - return countBrewPackages(prefix, result); - - countBrewPackages(FASTFETCH_TARGET_DIR_ROOT "/opt/homebrew", result); - countBrewPackages(FASTFETCH_TARGET_DIR_USR "/local", result); -} - -static uint32_t countMacPortsPackages(const char* dirname) -{ - FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateS(dirname); - ffStrbufAppendS(&baseDir, "/var/macports/software"); - - return ffPackagesGetNumElements(baseDir.chars, true); -} - -static uint32_t getMacPortsPackages() -{ - const char* prefix = getenv("MACPORTS_PREFIX"); - if(ffStrSet(prefix)) - return countMacPortsPackages(prefix); - - return countMacPortsPackages(FASTFETCH_TARGET_DIR_ROOT "/opt/local"); -} - -static uint32_t getNixPackagesImpl(const char* path) -{ - //Nix detection is kinda slow, so we only do it if the dir exists - if(!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) - return 0; - - FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(128); - - //https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222 - FF_STRBUF_AUTO_DESTROY command = ffStrbufCreateA(255); - ffStrbufAppendS(&command, "for x in $(nix-store --query --requisites "); - ffStrbufAppendS(&command, path); - ffStrbufAppendS(&command, "); do if [ -d $x ]; then echo $x ; fi ; done | cut -d- -f2- | egrep '([0-9]{1,}\\.)+[0-9]{1,}' | egrep -v '\\-doc$|\\-man$|\\-info$|\\-dev$|\\-bin$|^nixos-system-nixos-' | uniq | wc -l"); - - ffProcessAppendStdOut(&output, (char* const[]) { - FASTFETCH_TARGET_DIR_ROOT "/bin/sh", - "-c", - command.chars, - NULL - }); - - return (uint32_t) strtoul(output.chars, NULL, 10); -} - -static uint32_t getNixPackages(FFstrbuf* baseDir, const char* dirname) -{ uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - uint32_t num_elements = getNixPackagesImpl(baseDir->chars); + + ffStrbufAppendS(baseDir, "/Caskroom"); + result->brewCask += ffPackagesGetNumElements(baseDir->chars, true); ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; + + ffStrbufAppendS(baseDir, "/Cellar"); + result->brew += ffPackagesGetNumElements(baseDir->chars, true); + ffStrbufSubstrBefore(baseDir, baseDirLength); +} + +static uint32_t getMacPortsPackages(FFstrbuf* baseDir) +{ + ffStrbufAppendS(baseDir, "/var/macports/software"); + return ffPackagesGetNumElements(baseDir->chars, true); } void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) { - if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT)) getBrewPackages(result); - if (!(options->disabled & FF_PACKAGES_FLAG_MACPORTS_BIT)) result->macports = getMacPortsPackages(); + FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreate(); + if (!(options->disabled & FF_PACKAGES_FLAG_BREW_BIT)) + { + const char* prefix = getenv("HOMEBREW_PREFIX"); + if (ffStrSet(prefix)) + { + ffStrbufSetS(&baseDir, prefix); + } + else + { + #ifdef __aarch64__ + ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT "/opt/homebrew"); + #else + ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_USR "/local"); + #endif + } + countBrewPackages(&baseDir, result); + } + if (!(options->disabled & FF_PACKAGES_FLAG_MACPORTS_BIT)) + { + const char* prefix = getenv("MACPORTS_PREFIX"); + if (ffStrSet(prefix)) + { + ffStrbufSetS(&baseDir, prefix); + } + else + { + ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT "/opt/local"); + } + + result->macports = getMacPortsPackages(&baseDir); + } if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { - FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateS(FASTFETCH_TARGET_DIR_ROOT); - result->nixDefault += getNixPackages(&baseDir, "/nix/var/nix/profiles/default"); - result->nixSystem += getNixPackages(&baseDir, "/run/current-system"); + ffStrbufSetS(&baseDir, FASTFETCH_TARGET_DIR_ROOT); + result->nixDefault += ffPackagesGetNix(&baseDir, "/nix/var/nix/profiles/default"); + result->nixSystem += ffPackagesGetNix(&baseDir, "/run/current-system"); ffStrbufSet(&baseDir, &instance.state.platform.homeDir); - result->nixUser = getNixPackages(&baseDir, "/.nix-profile"); + result->nixUser = ffPackagesGetNix(&baseDir, "/.nix-profile"); } } diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index 9fdb29485..2fb000300 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -1,7 +1,6 @@ #include "packages.h" #include "common/io/io.h" #include "common/parsing.h" -#include "common/processing.h" #include "common/properties.h" #include "common/settings.h" #include "detection/os/os.h" @@ -124,157 +123,6 @@ static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, cons return sum; } -static bool isValidNixPkg(FFstrbuf* pkg) -{ - if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) - return false; - - ffStrbufSubstrAfterLastC(pkg, '/'); - if ( - ffStrbufStartsWithS(pkg, "nixos-system-nixos-") || - ffStrbufEndsWithS(pkg, "-doc") || - ffStrbufEndsWithS(pkg, "-man") || - ffStrbufEndsWithS(pkg, "-info") || - ffStrbufEndsWithS(pkg, "-dev") || - ffStrbufEndsWithS(pkg, "-bin") - ) return false; - - enum { START, DIGIT, DOT, MATCH } state = START; - - for (uint32_t i = 0; i < pkg->length; i++) - { - char c = pkg->chars[i]; - switch (state) - { - case START: - if (ffCharIsDigit(c)) - state = DIGIT; - break; - case DIGIT: - if (ffCharIsDigit(c)) - continue; - if (c == '.') - state = DOT; - else - state = START; - break; - case DOT: - if (ffCharIsDigit(c)) - state = MATCH; - else - state = START; - break; - case MATCH: - break; - } - } - - return state == MATCH; -} - -static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) -{ - if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) - return false; - - FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); - if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) - return false; - - // Format: \n - uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n'); - if (split == cacheContent.length) - return false; - - ffStrbufSetNS(hash, split, cacheContent.chars); - *count = (uint32_t)atoi(cacheContent.chars + split + 1); - - return true; -} - -static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) -{ - FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash); - ffStrbufAppendF(&cacheContent, "\n%u", count); - return ffWriteFileBuffer(cacheDir->chars, &cacheContent); -} - -static uint32_t getNixPackagesImpl(char* path) -{ - //Nix detection is kinda slow, so we only do it if the dir exists - if(!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) - return 0; - - FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir); - ffStrbufEnsureEndsWithC(&cacheDir, '/'); - ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix"); - ffStrbufAppendS(&cacheDir, path); - - //Check the hash first to determine if we need to recompute the count - FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64); - FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64); - uint32_t count = 0; - - ffProcessAppendStdOut(&hash, (char* const[]) { - "nix-store", - "--query", - "--hash", - path, - NULL - }); - - if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) - return count; - - //Cache is invalid, recompute the count - count = 0; - - //Implementation based on bash script from here: - //https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222 - - FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024); - - ffProcessAppendStdOut(&output, (char* const[]) { - "nix-store", - "--query", - "--requisites", - path, - NULL - }); - - uint32_t lineLength = 0; - for (uint32_t i = 0; i < output.length; i++) - { - if (output.chars[i] != '\n') - { - lineLength++; - continue; - } - - output.chars[i] = '\0'; - FFstrbuf line = { - .allocated = 0, - .length = lineLength, - .chars = output.chars + i - lineLength - }; - if (isValidNixPkg(&line)) - count++; - lineLength = 0; - } - - writeNixCache(&cacheDir, &hash, count); - return count; -} - -static uint32_t getNixPackages(FFstrbuf* baseDir, const char* dirname) -{ - uint32_t baseDirLength = baseDir->length; - ffStrbufAppendS(baseDir, dirname); - uint32_t num_elements = getNixPackagesImpl(baseDir->chars); - ffStrbufSubstrBefore(baseDir, baseDirLength); - return num_elements; -} - static uint32_t getXBPSImpl(FFstrbuf* baseDir) { DIR* dir = opendir(baseDir->chars); @@ -572,8 +420,8 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { - packageCounts->nixDefault += getNixPackages(baseDir, "/nix/var/nix/profiles/default"); - packageCounts->nixSystem += getNixPackages(baseDir, "/run/current-system"); + packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); + packageCounts->nixSystem += ffPackagesGetNix(baseDir, "/run/current-system"); } if (!(options->disabled & FF_PACKAGES_FLAG_PACMAN_BIT)) packageCounts->pacman += getNumElements(baseDir, "/var/lib/pacman/local", true); if (!(options->disabled & FF_PACKAGES_FLAG_LPKGBUILD_BIT)) packageCounts->lpkgbuild += getNumElements(baseDir, "/opt/Loc-OS-LPKG/lpkgbuild/remove", false); @@ -673,7 +521,7 @@ void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { // Count packages from $HOME/.nix-profile - result->nixUser += getNixPackages(&baseDir, ".nix-profile"); + result->nixUser += ffPackagesGetNix(&baseDir, ".nix-profile"); // Check in $XDG_STATE_HOME/nix/profile FF_STRBUF_AUTO_DESTROY stateHome = ffStrbufCreate(); @@ -688,11 +536,11 @@ void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options) ffStrbufSet(&stateHome, &instance.state.platform.homeDir); ffStrbufAppendS(&stateHome, ".local/state/"); } - result->nixUser += getNixPackages(&stateHome, "nix/profile"); + result->nixUser += ffPackagesGetNix(&stateHome, "nix/profile"); // Check in /etc/profiles/per-user/$USER FF_STRBUF_AUTO_DESTROY userPkgsDir = ffStrbufCreateStatic("/etc/profiles/per-user/"); - result->nixUser += getNixPackages(&userPkgsDir, instance.state.platform.userName.chars); + result->nixUser += ffPackagesGetNix(&userPkgsDir, instance.state.platform.userName.chars); } if (!(options->disabled & FF_PACKAGES_FLAG_GUIX_BIT)) diff --git a/src/detection/packages/packages_nix.c b/src/detection/packages/packages_nix.c new file mode 100644 index 000000000..08a5479a6 --- /dev/null +++ b/src/detection/packages/packages_nix.c @@ -0,0 +1,155 @@ +#include "packages.h" +#include "common/io/io.h" +#include "common/processing.h" +#include "util/stringUtils.h" + +static bool isValidNixPkg(FFstrbuf* pkg) +{ + if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) + return false; + + ffStrbufSubstrAfterLastC(pkg, '/'); + if ( + ffStrbufStartsWithS(pkg, "nixos-system-nixos-") || + ffStrbufEndsWithS(pkg, "-doc") || + ffStrbufEndsWithS(pkg, "-man") || + ffStrbufEndsWithS(pkg, "-info") || + ffStrbufEndsWithS(pkg, "-dev") || + ffStrbufEndsWithS(pkg, "-bin") + ) return false; + + enum { START, DIGIT, DOT, MATCH } state = START; + + for (uint32_t i = 0; i < pkg->length; i++) + { + char c = pkg->chars[i]; + switch (state) + { + case START: + if (ffCharIsDigit(c)) + state = DIGIT; + break; + case DIGIT: + if (ffCharIsDigit(c)) + continue; + if (c == '.') + state = DOT; + else + state = START; + break; + case DOT: + if (ffCharIsDigit(c)) + state = MATCH; + else + state = START; + break; + case MATCH: + break; + } + } + + return state == MATCH; +} + +static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) +{ + if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) + return false; + + FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate(); + if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) + return false; + + // Format: \n + uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n'); + if (split == cacheContent.length) + return false; + + ffStrbufSetNS(hash, split, cacheContent.chars); + *count = (uint32_t)atoi(cacheContent.chars + split + 1); + + return true; +} + +static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) +{ + FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash); + ffStrbufAppendF(&cacheContent, "\n%u", count); + return ffWriteFileBuffer(cacheDir->chars, &cacheContent); +} + +static uint32_t getNixPackagesImpl(char* path) +{ + //Nix detection is kinda slow, so we only do it if the dir exists + if(!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) + return 0; + + FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir); + ffStrbufEnsureEndsWithC(&cacheDir, '/'); + ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix"); + ffStrbufAppendS(&cacheDir, path); + + //Check the hash first to determine if we need to recompute the count + FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64); + FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64); + uint32_t count = 0; + + ffProcessAppendStdOut(&hash, (char* const[]) { + "nix-store", + "--query", + "--hash", + path, + NULL + }); + + if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) + return count; + + //Cache is invalid, recompute the count + count = 0; + + //Implementation based on bash script from here: + //https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222 + + FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024); + + ffProcessAppendStdOut(&output, (char* const[]) { + "nix-store", + "--query", + "--requisites", + path, + NULL + }); + + uint32_t lineLength = 0; + for (uint32_t i = 0; i < output.length; i++) + { + if (output.chars[i] != '\n') + { + lineLength++; + continue; + } + + output.chars[i] = '\0'; + FFstrbuf line = { + .allocated = 0, + .length = lineLength, + .chars = output.chars + i - lineLength + }; + if (isValidNixPkg(&line)) + count++; + lineLength = 0; + } + + writeNixCache(&cacheDir, &hash, count); + return count; +} + +uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname) +{ + uint32_t baseDirLength = baseDir->length; + ffStrbufAppendS(baseDir, dirname); + uint32_t num_elements = getNixPackagesImpl(baseDir->chars); + ffStrbufSubstrBefore(baseDir, baseDirLength); + return num_elements; +}