From 5c0ad030cc49f49b26e90598e4a3785d12ebe970 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 19 Jul 2025 16:27:38 +0800 Subject: [PATCH 01/41] GPU (Linux): support advanced detection for nouveau driver --- src/detection/gpu/gpu.h | 1 + src/detection/gpu/gpu_bsd.c | 2 ++ src/detection/gpu/gpu_drm.c | 24 ++++++++++++++++++++++++ src/detection/gpu/gpu_linux.c | 20 ++++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/src/detection/gpu/gpu.h b/src/detection/gpu/gpu.h index 1ebee2de6..e4db0f858 100644 --- a/src/detection/gpu/gpu.h +++ b/src/detection/gpu/gpu.h @@ -67,6 +67,7 @@ const char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, con const char* ffDrmDetectI915(FFGPUResult* gpu, int fd); const char* ffDrmDetectXe(FFGPUResult* gpu, int fd); const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd); +const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd); #endif // FF_HAVE_DRM const char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId); diff --git a/src/detection/gpu/gpu_bsd.c b/src/detection/gpu/gpu_bsd.c index 3d4605546..b4c263699 100644 --- a/src/detection/gpu/gpu_bsd.c +++ b/src/detection/gpu/gpu_bsd.c @@ -118,6 +118,8 @@ static const char* detectByDrm(const FFGPUOptions* options, FFlist* gpus) ffDrmDetectXe(gpu, fd); else if (ffStrStartsWith(driverName, "asahi")) ffDrmDetectAsahi(gpu, fd); + else if (ffStrStartsWith(driverName, "nouveau")) + ffDrmDetectNouveau(gpu, fd); else if (dev->bustype == DRM_BUS_PCI) { ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { diff --git a/src/detection/gpu/gpu_drm.c b/src/detection/gpu/gpu_drm.c index 5ddea2799..a1c9cd0c3 100644 --- a/src/detection/gpu/gpu_drm.c +++ b/src/detection/gpu/gpu_drm.c @@ -13,6 +13,7 @@ #include "intel_drm.h" #include "asahi_drm.h" #include +#include const char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath) { @@ -343,6 +344,29 @@ const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd) return "Failed to query Asahi GPU information"; } +#ifndef DRM_IOCTL_NOUVEAU_GETPARAM +#define DRM_IOCTL_NOUVEAU_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_NOUVEAU_GETPARAM, struct drm_nouveau_getparam) +#endif + +const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd) +{ + struct drm_nouveau_getparam getparam = { }; + + getparam.param = NOUVEAU_GETPARAM_FB_SIZE; + if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0) + gpu->dedicated.total = getparam.value; + + getparam.param = NOUVEAU_GETPARAM_AGP_SIZE; + if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0) + gpu->shared.total = getparam.value; + + getparam.param = NOUVEAU_GETPARAM_GRAPH_UNITS; + if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0 && getparam.value < INT32_MAX) + gpu->coreCount = (int32_t) getparam.value; + + return NULL; +} + #endif // FF_HAVE_DRM #include "gpu_driver_specific.h" diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c index c37502e47..39fca1478 100644 --- a/src/detection/gpu/gpu_linux.c +++ b/src/detection/gpu/gpu_linux.c @@ -263,6 +263,21 @@ static const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, #endif } +static const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) +{ + #if FF_HAVE_DRM + ffStrbufSetS(buffer, "/dev/dri/"); + ffStrbufAppendS(buffer, drmKey); + FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC); + if (fd < 0) return "Failed to open drm device"; + + return ffDrmDetectNouveau(gpu, fd); + #else + FF_UNUSED(gpu, drmKey, buffer); + return "Fastfetch is not compiled with drm support"; + #endif +} + static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf* buffer, FFstrbuf* deviceDir, const char* drmKey) { const uint32_t drmDirPathLength = deviceDir->length; @@ -369,6 +384,11 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf if (options->driverSpecific && drmKey) drmDetectIntelSpecific(gpu, drmKey, buffer); } + else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, "nouveau")) + { + if (options->driverSpecific && drmKey) + drmDetectNouveauSpecific(gpu, drmKey, buffer); + } else { ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { From 847b76b8635568f6c8ecaa977860cd3f68f30b8a Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 19 Jul 2025 18:17:28 +0800 Subject: [PATCH 02/41] GPU (Linux): add GPU temp detection for nouveau driver --- src/detection/gpu/gpu_linux.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c index 39fca1478..a43018e35 100644 --- a/src/detection/gpu/gpu_linux.c +++ b/src/detection/gpu/gpu_linux.c @@ -263,6 +263,34 @@ static const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, #endif } +static const char* pciDetectNouveauSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) +{ + if (options->temp) + { + const uint32_t pciDirLen = pciDir->length; + ffStrbufAppendS(pciDir, "/hwmon/"); + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars); + if (dirp) + { + struct dirent* entry; + while ((entry = readdir(dirp))) + { + if (entry->d_name[0] == '.') continue; + ffStrbufAppendS(pciDir, entry->d_name); + ffStrbufAppendS(pciDir, "/temp1_input"); + if (ffReadFileBuffer(pciDir->chars, buffer)) + { + uint64_t value = ffStrbufToUInt(buffer, 0); + if (value > 0) gpu->temperature = (double) value / 1000.0; + } + break; + } + } + ffStrbufSubstrBefore(pciDir, pciDirLen); + } + return NULL; +} + static const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) { #if FF_HAVE_DRM @@ -386,6 +414,7 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, "nouveau")) { + pciDetectNouveauSpecific(options, gpu, deviceDir, buffer); if (options->driverSpecific && drmKey) drmDetectNouveauSpecific(gpu, drmKey, buffer); } From 7566f10bb6463bee127434f5d18892fa2017a507 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sun, 20 Jul 2025 16:47:35 +0800 Subject: [PATCH 03/41] Logo (Builtin): add Aeon Fix #1855 --- src/logo/ascii/aeon.txt | 14 ++++++++++++++ src/logo/builtin.c | 9 +++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/logo/ascii/aeon.txt diff --git a/src/logo/ascii/aeon.txt b/src/logo/ascii/aeon.txt new file mode 100644 index 000000000..902af06da --- /dev/null +++ b/src/logo/ascii/aeon.txt @@ -0,0 +1,14 @@ +⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷ +⣿⡇ ⢸⣿ +⣿⡇ ⢀⣀ ⣀⡀ ⢸⣿ +⣿⣇ ⠸⣿⣄ ⣠⣿⠇ ⣸⣿ +⢹⣿⡄ ⠙⠻⠿⠿⠟⠋ ⢠⣿⡏ + ⠹⣿⣦⡀ ⢀⣴⣿⠏ + ⠈⠛⢿⣶⣤⣄ ⣠⣤⣶⡿⠛⠁ +$2 ⣠⣴⡿⠿⠛ ⠛⠿⢿⣦⣄ + ⣠⣾⠟⠉ ⠉⠻⣷⣄ +⢰⣿⠏ ⢀⣤⣶⣶⣤⡀ ⠹⣿⡆ +⣿⡟ ⢰⣿⠏⠁⠈⠹⣿⡆ ⢿⣿ +⣿⡇ ⠈⠋ ⠙⠁ ⢸⣿ +⣿⡇ ⢸⣿ +⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿ \ No newline at end of file diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 3ea22dfd4..0322b1320 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -30,6 +30,15 @@ static const FFlogo A[] = { FF_COLOR_FG_CYAN, }, }, + // Aeon + { + .names = {"Aeon"}, + .lines = FASTFETCH_DATATEXT_LOGO_AEON, + .colors = { + FF_COLOR_FG_256 "36", + FF_COLOR_FG_256 "36", + }, + }, // Afterglow { .names = {"Afterglow"}, From 272c0aad9cc0f193292ec33c410346e780d3e5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 21 Jul 2025 14:06:19 +0800 Subject: [PATCH 04/41] Packages (Linux): improve accuracy of flatpak count detection Fix #1856 --- src/detection/packages/packages_linux.c | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index 19543a875..776157689 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -519,6 +519,31 @@ static inline uint32_t getFlatpakRuntimePackages(FFstrbuf* baseDir) return num_elements; } +static inline uint32_t getFlatpakAppPackages(FFstrbuf* baseDir) +{ + ffStrbufAppendS(baseDir, "app/"); + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(baseDir->chars); + if (dirp == NULL) + return 0; + + uint32_t appDirLength = baseDir->length; + uint32_t num_elements = 0; + + struct dirent *entry; + while ((entry = readdir(dirp)) != NULL) + { + if(entry->d_type == DT_DIR && entry->d_name[0] != '.') + { + ffStrbufAppendS(baseDir, entry->d_name); + ffStrbufAppendS(baseDir, "/current"); + if (ffPathExists(baseDir->chars, FF_PATHTYPE_ANY)) // Exclude deleted apps, #1856 + ++num_elements; + ffStrbufSubstrBefore(baseDir, appDirLength); + } + } + return num_elements; +} + static uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname) { uint32_t num_elements = 0; @@ -527,8 +552,7 @@ static uint32_t getFlatpakPackages(FFstrbuf* baseDir, const char* dirname) ffStrbufAppendS(baseDir, "/flatpak/"); uint32_t flatpakDirLength = baseDir->length; - ffStrbufAppendS(baseDir, "app"); - num_elements += ffPackagesGetNumElements(baseDir->chars, true); + num_elements += getFlatpakAppPackages(baseDir); ffStrbufSubstrBefore(baseDir, flatpakDirLength); num_elements += getFlatpakRuntimePackages(baseDir); From b60996bcd1a7caf53b310fc5cc384bc6f1c794c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 21 Jul 2025 14:19:04 +0800 Subject: [PATCH 05/41] Packages (Linux): remove `qi` support Close #1858 --- doc/json_schema.json | 2 +- src/detection/packages/packages.h | 1 - src/detection/packages/packages_linux.c | 1 - src/modules/packages/option.h | 9 ++++----- src/modules/packages/packages.c | 11 ----------- 5 files changed, 5 insertions(+), 19 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index a6a8165d4..a91724008 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -401,7 +401,7 @@ "type": "string" }, "packagesFormat": { - "description": "Output format of the module `Packages`. See Wiki for formatting syntax\n 1. {all}: Number of all packages\n 2. {pacman}: Number of pacman packages\n 3. {pacman-branch}: Pacman branch on manjaro\n 4. {dpkg}: Number of dpkg packages\n 5. {rpm}: Number of rpm packages\n 6. {emerge}: Number of emerge packages\n 7. {eopkg}: Number of eopkg packages\n 8. {xbps}: Number of xbps packages\n 9. {nix-system}: Number of nix-system packages\n 10. {nix-user}: Number of nix-user packages\n 11. {nix-default}: Number of nix-default packages\n 12. {apk}: Number of apk packages\n 13. {pkg}: Number of pkg packages\n 14. {flatpak-system}: Number of flatpak-system app packages\n 15. {flatpak-user}: Number of flatpak-user app packages\n 16. {snap}: Number of snap packages\n 17. {brew}: Number of brew packages\n 18. {brew-cask}: Number of brew-cask packages\n 19. {macports}: Number of macports packages\n 20. {scoop}: Number of scoop packages\n 21. {choco}: Number of choco packages\n 22. {pkgtool}: Number of pkgtool packages\n 23. {paludis}: Number of paludis packages\n 24. {winget}: Number of winget packages\n 25. {opkg}: Number of opkg packages\n 26. {am-system}: Number of am-system packages\n 27. {sorcery}: Number of sorcery packages\n 28. {lpkg}: Number of lpkg packages\n 29. {lpkgbuild}: Number of lpkgbuild packages\n 30. {guix-system}: Number of guix-system packages\n 31. {guix-user}: Number of guix-user packages\n 32. {guix-home}: Number of guix-home packages\n 33. {linglong}: Number of linglong packages\n 34. {pacstall}: Number of pacstall packages\n 35. {mport}: Number of mport packages\n 36. {qi}: Number of qi packages\n 37. {am-user}: Number of am-user (aka appman) packages\n 38. {pkgsrc}: Number of pkgsrc packages\n 39. {hpkg-system}: Number of hpkg-system packages\n 40. {hpkg-user}: Number of hpkg-user packages\n 41. {pisi}: Number of pisi packages\n 42. {soar}: Number of soar packages\n 43. {nix-all}: Total number of all nix packages\n 44. {flatpak-all}: Total number of all flatpak app packages\n 45. {brew-all}: Total number of all brew packages\n 46. {guix-all}: Total number of all guix packages\n 47. {hpkg-all}: Total number of all hpkg packages", + "description": "Output format of the module `Packages`. See Wiki for formatting syntax\n 1. {all}: Number of all packages\n 2. {pacman}: Number of pacman packages\n 3. {pacman-branch}: Pacman branch on manjaro\n 4. {dpkg}: Number of dpkg packages\n 5. {rpm}: Number of rpm packages\n 6. {emerge}: Number of emerge packages\n 7. {eopkg}: Number of eopkg packages\n 8. {xbps}: Number of xbps packages\n 9. {nix-system}: Number of nix-system packages\n 10. {nix-user}: Number of nix-user packages\n 11. {nix-default}: Number of nix-default packages\n 12. {apk}: Number of apk packages\n 13. {pkg}: Number of pkg packages\n 14. {flatpak-system}: Number of flatpak-system app packages\n 15. {flatpak-user}: Number of flatpak-user app packages\n 16. {snap}: Number of snap packages\n 17. {brew}: Number of brew packages\n 18. {brew-cask}: Number of brew-cask packages\n 19. {macports}: Number of macports packages\n 20. {scoop}: Number of scoop packages\n 21. {choco}: Number of choco packages\n 22. {pkgtool}: Number of pkgtool packages\n 23. {paludis}: Number of paludis packages\n 24. {winget}: Number of winget packages\n 25. {opkg}: Number of opkg packages\n 26. {am-system}: Number of am-system packages\n 27. {sorcery}: Number of sorcery packages\n 28. {lpkg}: Number of lpkg packages\n 29. {lpkgbuild}: Number of lpkgbuild packages\n 30. {guix-system}: Number of guix-system packages\n 31. {guix-user}: Number of guix-user packages\n 32. {guix-home}: Number of guix-home packages\n 33. {linglong}: Number of linglong packages\n 34. {pacstall}: Number of pacstall packages\n 35. {mport}: Number of mport packages\n 36. {am-user}: Number of am-user (aka appman) packages\n 37. {pkgsrc}: Number of pkgsrc packages\n 38. {hpkg-system}: Number of hpkg-system packages\n 39. {hpkg-user}: Number of hpkg-user packages\n 40. {pisi}: Number of pisi packages\n 41. {soar}: Number of soar packages\n 42. {nix-all}: Total number of all nix packages\n 43. {flatpak-all}: Total number of all flatpak app packages\n 44. {brew-all}: Total number of all brew packages\n 45. {guix-all}: Total number of all guix packages\n 46. {hpkg-all}: Total number of all hpkg packages", "type": "string" }, "physicaldiskFormat": { diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h index ad7dd9341..47c948f8c 100644 --- a/src/detection/packages/packages.h +++ b/src/detection/packages/packages.h @@ -36,7 +36,6 @@ typedef struct FFPackagesResult uint32_t pkg; uint32_t pkgsrc; uint32_t pkgtool; - uint32_t qi; uint32_t rpm; uint32_t scoop; uint32_t snap; diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index 776157689..9fdb29485 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -600,7 +600,6 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, } if (!(options->disabled & FF_PACKAGES_FLAG_LINGLONG_BIT)) packageCounts->linglong += getNumElements(baseDir, "/var/lib/linglong/repo/refs/heads/main", true); if (!(options->disabled & FF_PACKAGES_FLAG_PACSTALL_BIT)) packageCounts->pacstall += getNumElements(baseDir, "/var/lib/pacstall/metadata", false); - if (!(options->disabled & FF_PACKAGES_FLAG_QI_BIT)) packageCounts->qi += getNumStrings(baseDir, "/var/qi/installed_packages.list", "\n", "qi"); if (!(options->disabled & FF_PACKAGES_FLAG_PISI_BIT)) packageCounts->pisi += getNumElements(baseDir, "/var/lib/pisi/package", true); if (!(options->disabled & FF_PACKAGES_FLAG_PKGSRC_BIT)) packageCounts->pkgsrc += getNumElements(baseDir, "/usr/pkg/pkgdb", DT_DIR); } diff --git a/src/modules/packages/option.h b/src/modules/packages/option.h index 40e5b1413..5709578d4 100644 --- a/src/modules/packages/option.h +++ b/src/modules/packages/option.h @@ -34,11 +34,10 @@ typedef enum __attribute__((__packed__)) FFPackagesFlags FF_PACKAGES_FLAG_LINGLONG_BIT = 1ULL << 24, FF_PACKAGES_FLAG_PACSTALL_BIT = 1ULL << 25, FF_PACKAGES_FLAG_MPORT_BIT = 1ULL << 26, - FF_PACKAGES_FLAG_QI_BIT = 1ULL << 27, - FF_PACKAGES_FLAG_PKGSRC_BIT = 1ULL << 28, - FF_PACKAGES_FLAG_HPKG_BIT = 1ULL << 29, - FF_PACKAGES_FLAG_PISI_BIT = 1ULL << 30, - FF_PACKAGES_FLAG_SOAR_BIT = 1ULL << 31, + FF_PACKAGES_FLAG_PKGSRC_BIT = 1ULL << 27, + FF_PACKAGES_FLAG_HPKG_BIT = 1ULL << 28, + FF_PACKAGES_FLAG_PISI_BIT = 1ULL << 29, + FF_PACKAGES_FLAG_SOAR_BIT = 1ULL << 30, FF_PACKAGES_FLAG_FORCE_UNSIGNED = UINT64_MAX, } FFPackagesFlags; static_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), ""); diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index 190347d3e..4fb26080a 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -135,7 +135,6 @@ void ffPrintPackages(FFPackagesOptions* options) FF_PRINT_PACKAGE(linglong) FF_PRINT_PACKAGE(pacstall) FF_PRINT_PACKAGE(mport) - FF_PRINT_PACKAGE(qi) FF_PRINT_PACKAGE(pisi) FF_PRINT_PACKAGE(soar) @@ -179,7 +178,6 @@ void ffPrintPackages(FFPackagesOptions* options) FF_FORMAT_ARG(counts.linglong, "linglong"), FF_FORMAT_ARG(counts.pacstall, "pacstall"), FF_FORMAT_ARG(counts.mport, "mport"), - FF_FORMAT_ARG(counts.qi, "qi"), FF_FORMAT_ARG(counts.amUser, "am-user"), FF_FORMAT_ARG(counts.pkgsrc, "pkgsrc"), FF_FORMAT_ARG(counts.hpkgSystem, "hpkg-system"), @@ -269,9 +267,6 @@ bool ffParsePackagesCommandOptions(FFPackagesOptions* options, const char* key, FF_TEST_PACKAGE_NAME(PKGTOOL) FF_TEST_PACKAGE_NAME(PKGSRC) break; - case 'Q': if (false); - FF_TEST_PACKAGE_NAME(QI) - break; case 'R': if (false); FF_TEST_PACKAGE_NAME(RPM) break; @@ -395,9 +390,6 @@ void ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module) FF_TEST_PACKAGE_NAME(PKGTOOL) FF_TEST_PACKAGE_NAME(PKGSRC) break; - case 'Q': if (false); - FF_TEST_PACKAGE_NAME(QI) - break; case 'R': if (false); FF_TEST_PACKAGE_NAME(RPM) break; @@ -466,7 +458,6 @@ void ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* do FF_TEST_PACKAGE_NAME(PKG) FF_TEST_PACKAGE_NAME(PKGTOOL) FF_TEST_PACKAGE_NAME(PKGSRC) - FF_TEST_PACKAGE_NAME(QI) FF_TEST_PACKAGE_NAME(RPM) FF_TEST_PACKAGE_NAME(SCOOP) FF_TEST_PACKAGE_NAME(SNAP) @@ -528,7 +519,6 @@ void ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions* options, yy FF_APPEND_PACKAGE_COUNT(pkg) FF_APPEND_PACKAGE_COUNT(pkgtool) FF_APPEND_PACKAGE_COUNT(pkgsrc) - FF_APPEND_PACKAGE_COUNT(qi) FF_APPEND_PACKAGE_COUNT(macports) FF_APPEND_PACKAGE_COUNT(rpm) FF_APPEND_PACKAGE_COUNT(scoop) @@ -584,7 +574,6 @@ static FFModuleBaseInfo ffModuleInfo = { {"Number of linglong packages", "linglong"}, {"Number of pacstall packages", "pacstall"}, {"Number of mport packages", "mport"}, - {"Number of qi packages", "qi"}, {"Number of am-user (aka appman) packages", "am-user"}, {"Number of pkgsrc packages", "pkgsrc"}, {"Number of hpkg-system packages", "hpkg-system"}, From d07fe997a9b6eadf9731ec9efae1819cdd235885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 21 Jul 2025 14:41:02 +0800 Subject: [PATCH 06/41] CI (NetBSD): remove opencl support to fix building --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83e824db6..a53459d25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -628,7 +628,7 @@ jobs: version: '10.1' run: | uname -a - sudo pkgin -y install cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick opencl-headers ocl-icd + sudo pkgin -y install cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On . cmake --build . --target package --verbose -j4 ./fastfetch --list-features From ccbb9e90ea6d33c41ce4e9883970ad8db30ab284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 21 Jul 2025 20:23:38 +0800 Subject: [PATCH 07/41] Netif (Windows): fix default route detection when the default adapter is disabled This commit addresses an issue where the default route detection on Windows incorrectly identified a disabled adapter as the default route. Previously, when a user disabled the adapter configured as the default route, the system still reported it as active due to stale routing table entries. --- src/common/netif/netif_windows.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/netif/netif_windows.c b/src/common/netif/netif_windows.c index 6042dd8d6..54526864b 100644 --- a/src/common/netif/netif_windows.c +++ b/src/common/netif/netif_windows.c @@ -18,6 +18,8 @@ bool ffNetifGetDefaultRouteImpl(FF_MAYBE_UNUSED char iface[IF_NAMESIZE + 1], uin { MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i]; + if (row->Age > row->ValidLifetime) continue; + if ((row->DestinationPrefix.PrefixLength == 0) && ((row->DestinationPrefix.Prefix.Ipv4.sin_family == AF_INET && row->DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr == 0) || From be3e2ba521d6dde302933cdaa2d58691b2c650e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 22 Jul 2025 15:13:12 +0800 Subject: [PATCH 08/41] Presets: improve `25.jsonc`; add verbose comments [ci skip] --- presets/examples/25.jsonc | 109 +++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 24 deletions(-) diff --git a/presets/examples/25.jsonc b/presets/examples/25.jsonc index 85f4835f7..5d0ce75bb 100644 --- a/presets/examples/25.jsonc +++ b/presets/examples/25.jsonc @@ -6,11 +6,13 @@ "keys": "blue" }, "separator": "", + // Constants are reusable strings referenced by {$1}, {$2}, etc. + // These contain ANSI escape codes for cursor positioning "constants": [ - "──────────────────────────────────────────────", - "\u001b[47D", - "\u001b[47C", - "\u001b[46C" + "──────────────────────────────────────────────", // {$1} - horizontal line for borders + "\u001b[47D", // {$2} - move cursor left 47 columns + "\u001b[47C", // {$3} - move cursor right 47 columns + "\u001b[46C" // {$4} - move cursor right 46 columns ], "brightColor": false }, @@ -22,6 +24,24 @@ }, { "type": "os", + // Key format breakdown for OS module: + // "│ {icon} \u001b[s{sysname}\u001b[u\u001b[10C│{$3}│{$2}" + // + // │ - Left border of key block + // {icon} - OS icon (defined internally by fastfetch) + // \u001b[s - ANSI escape: save cursor position (ESC[s) + // {sysname} - Format variable: system name (e.g., "Linux", "Darwin") + // \u001b[u - ANSI escape: restore cursor to saved position (ESC[u) + // Necessary because the length of `{sysname}` differs between different platforms + // \u001b[10C - ANSI escape: move cursor right 10 columns (ESC[10C) + // │ - Right border of key block (always 10 columns from left border) + // {$3} - Reference to constants[2]: move cursor right 47 columns + // │ - Right border of value block + // {$2} - Reference to constants[1]: move cursor left 47 columns + // + // This creates a fixed-width layout where the key block is exactly 10 columns wide, + // regardless of the actual content length. The cursor manipulation ensures proper + // alignment for the table-like structure. "key": "│ {icon} \u001b[s{sysname}\u001b[u\u001b[10C│{$3}│{$2}" }, { @@ -34,7 +54,7 @@ "key": "│ {icon} Locale │{$3}│{$2}" }, - // Hardware + // Hardware section with cyan color theme { "type": "custom", "key": "│{#cyan}┌──────────────┬{$1}┐{#keys}│\u001b[37D", @@ -42,6 +62,11 @@ }, { "type": "chassis", + // Similar structure but with cyan color formatting: + // │{#cyan}│ - Left border with cyan color + // {icon} - Chassis icon + // Chassis - Fixed label text + // │{$4}│{#keys}│{$2} - Positioning and borders for value area "key": "│{#cyan}│ {icon} Chassis │{$4}│{#keys}│{$2}" }, { @@ -76,7 +101,7 @@ "format": "" }, - // Desktop + // Desktop section with green color theme { "type": "custom", "key": "│{#green}┌──────────────┬{$1}┐{#keys}│\u001b[37D", @@ -106,7 +131,7 @@ "format": "" }, - // Terminal + // Terminal section with yellow color theme { "type": "custom", "key": "│{#yellow}┌──────────────┬{$1}┐{#keys}│\u001b[37D", @@ -138,7 +163,7 @@ "format": "" }, - // Development + // Development section with red color theme { "type": "custom", "key": "│{#red}┌──────────────┬{$1}┐{#keys}│\u001b[39D", @@ -146,31 +171,44 @@ }, { "type": "command", - "keyIcon": "", + "keyIcon": "", // Custom icon override "key": "│{#red}│ {icon} Rust │{$4}│{#keys}│{$2}", - "text": "rustc --version | cut -d' ' -f2", - "format": "rustc {}" + "text": "rustc --version", + "format": "rustc {~6,13}" // Print 6th to 13th characters (version number) }, { "type": "command", + "condition": { + "!system": "Windows" // Posix version + }, "keyIcon": "", "key": "│{#red}│ {icon} Clang │{$4}│{#keys}│{$2}", "text": "clang --version | head -1 | awk '{print $NF}'", "format": "clang {}" }, + { + "type": "command", + "condition": { + "system": "Windows" // Windows version + }, + "keyIcon": "", + "key": "│{#red}│ {icon} Clang │{$4}│{#keys}│{$2}", + "text": "clang --version | findstr version", // Finds the line with "version" + "format": "clang {~-6}" // Prints the last 6 characters (version number) + }, { "type": "command", "keyIcon": "", "key": "│{#red}│ {icon} NodeJS │{$4}│{#keys}│{$2}", "text": "node --version", - "format": "node {~1}" + "format": "node {~1}" // {~1} removes first character (v) }, { "type": "command", "keyIcon": "", "key": "│{#red}│ {icon} Go │{$4}│{#keys}│{$2}", "text": "go version | cut -d' ' -f3", - "format": "go {~2}" + "format": "go {~2}" // {~2} removes first 2 characters (go) }, { "type": "command", @@ -200,7 +238,7 @@ "format": "" }, - // Uptime + // Uptime section with magenta color theme { "type": "custom", "key": "│{#magenta}┌──────────────┬{$1}┐{#keys}│\u001b[36D", @@ -212,28 +250,28 @@ }, { "type": "users", - "myselfOnly": true, + "myselfOnly": true, // Only show current user "keyIcon": "", "key": "│{#magenta}│ {icon} Login │{$4}│{#keys}│{$2}" }, { - "condition": { + "condition": { // Conditional module: only show on non-macOS "!system": "macOS" }, "type": "disk", "keyIcon": "", "key": "│{#magenta}│ {icon} OS Age │{$4}│{#keys}│{$2}", - "folders": "/", - "format": "{create-time:10} [{days} days]" + "folders": "/", // Check root filesystem + "format": "{create-time:10} [{days} days]" // Show creation time and age in days }, { - "condition": { + "condition": { // Conditional module: only show on macOS "system": "macOS" }, "type": "disk", "keyIcon": "", "key": "│{#magenta}│ {icon} OS Age │{$4}│{#keys}│{$2}", - "folders": "/System/Volumes/VM", + "folders": "/System/Volumes/VM", // Work around for APFS on macOS "format": "{create-time:10} [{days} days]" }, { @@ -243,12 +281,35 @@ }, { "type": "custom", - "key": "└─────────────────{$1}┘", + "key": "└─────────────────{$1}┘", // Bottom border of the entire layout "format": "" }, - // End - "break", - "colors" + // End with color palette and line break + "break", // Add a blank line + "colors" // Display color palette ] } + +/* +Key Format Structure Explanation: + +The key format uses a combination of: +1. Unicode box drawing characters (│ ┌ ┐ └ ┘ ┬ ┴) for borders +2. ANSI escape codes for cursor positioning (\u001b[...) +3. Format variables ({icon}, {sysname}, etc.) +4. Constant references ({$1}, {$2}, etc.) +5. Color formatting ({#color}) + +ANSI Escape Codes Used: +- \u001b[s - Save cursor position (ESC[s) +- \u001b[u - Restore cursor position (ESC[u) +- \u001b[nC - Move cursor right n columns (ESC[nC) +- \u001b[nD - Move cursor left n columns (ESC[nD) + +This creates a table-like layout with fixed column widths and proper alignment, +regardless of the actual content length in each field. + +For more ANSI escape code reference, see: +https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#cursor-controls +*/ From 9b491c2f49a2eae5adde7bab364758ddfbf557f9 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 22 Jul 2025 22:03:53 +0800 Subject: [PATCH 09/41] Wifi (Linux): fix interface status when the interface is up but not connected --- src/detection/wifi/wifi_linux.c | 35 +++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c index deadf6dcc..34c3d9f08 100644 --- a/src/detection/wifi/wifi_linux.c +++ b/src/detection/wifi/wifi_linux.c @@ -554,21 +554,48 @@ const char* ffDetectWifi(FF_MAYBE_UNUSED FFlist* result) item->conn.channel = 0; item->conn.frequency = 0; + char operstate; ffStrbufSetF(&buffer, "/sys/class/net/%s/operstate", i->if_name); - if (!ffAppendFileBuffer(buffer.chars, &item->inf.status)) + if (!ffReadFileData(buffer.chars, 1, &operstate)) { FF_DEBUG("Failed to read operstate file"); continue; } - ffStrbufTrimRightSpace(&item->inf.status); - FF_DEBUG("Interface status: %s", item->inf.status.chars); - if (!ffStrbufEqualS(&item->inf.status, "up")) + FF_DEBUG("Connection status: %c", operstate); + if (operstate != 'u') { FF_DEBUG("Skipping interface as it's not up"); + ffStrbufSetStatic(&item->conn.status, "disconnected"); + + ffStrbufSetF(&buffer, "/sys/class/net/%s/flags", i->if_name); + char flags[16]; + ssize_t len = ffReadFileData(buffer.chars, sizeof(flags), flags); + if (len <= 0) + { + FF_DEBUG("Failed to read flags file"); + ffStrbufSetStatic(&item->inf.status, "unknown"); + continue; + } + flags[len] = '\0'; + FF_DEBUG("Interface flags: %s", flags); + unsigned flagsVal = (unsigned) strtoul(flags, NULL, 16); + if (flagsVal & IFF_UP) + { + ffStrbufSetStatic(&item->inf.status, "up"); + FF_DEBUG("Interface is up but not connected"); + } + else + { + ffStrbufSetStatic(&item->inf.status, "down"); + FF_DEBUG("Interface is down"); + } + continue; } + ffStrbufSetStatic(&item->inf.status, "up"); + FF_DEBUG("Trying to detect wifi with iw"); if (detectWifiWithIw(item, &buffer) != NULL) { From aa1201ffb858e7783f8d65dd0d47ced4978d6b14 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 22 Jul 2025 22:06:18 +0800 Subject: [PATCH 10/41] Editor: correct the variable name `full-path` to `path` Fix #1861 --- doc/json_schema.json | 2 +- src/modules/editor/editor.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index a91724008..b5e86a82d 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -317,7 +317,7 @@ "type": "string" }, "editorFormat": { - "description": "Output format of the module `Editor`. See Wiki for formatting syntax\n 1. {type}: Type (Visual / Editor)\n 2. {name}: Name\n 3. {exe-name}: Exe name of real path\n 4. {full-path}: Full path of real path\n 5. {version}: Version", + "description": "Output format of the module `Editor`. See Wiki for formatting syntax\n 1. {type}: Type (Visual / Editor)\n 2. {name}: Name\n 3. {exe-name}: Exe name of real path\n 4. {path}: Full path of real path\n 5. {version}: Version", "type": "string" }, "fontFormat": { diff --git a/src/modules/editor/editor.c b/src/modules/editor/editor.c index 11afc0940..42ff06a5e 100644 --- a/src/modules/editor/editor.c +++ b/src/modules/editor/editor.c @@ -130,7 +130,7 @@ static FFModuleBaseInfo ffModuleInfo = { {"Type (Visual / Editor)", "type"}, {"Name", "name"}, {"Exe name of real path", "exe-name"}, - {"Full path of real path", "full-path"}, + {"Full path of real path", "path"}, {"Version", "version"}, })) }; From 6cdd64ac55a7c5a53d870598d37f40f6c46f855e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 22 Jul 2025 22:10:00 +0800 Subject: [PATCH 11/41] CI (Haiku): remove work around --- .github/workflows/ci.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a53459d25..71629f5fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -690,34 +690,6 @@ jobs: - name: checkout repository uses: actions/checkout@v4 - - name: Fixup VM - uses: cross-platform-actions/action@master - with: - operating_system: haiku - version: 'r1beta5' - architecture: x86-64 - cpu_count: 4 - shell: bash - sync_files: false - shutdown_vm: false - run: | - mv '/home/runner/work' '/boot/home/work' - ln -sf '/boot/home/work' '/home/runner/work' - - - name: Sync files to VM - uses: cross-platform-actions/action@master - with: - operating_system: haiku - version: 'r1beta5' - architecture: x86-64 - cpu_count: 4 - shell: bash - sync_files: runner-to-vm - shutdown_vm: false - run: | - chown -R $(id -u):$(id -g) $(pwd) - ls -l - - name: run VM uses: cross-platform-actions/action@master with: @@ -726,7 +698,6 @@ jobs: architecture: x86-64 cpu_count: 4 shell: bash - sync_files: vm-to-runner run: | uname -a pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake gcc make pkgconfig python3.10 From a90e1d7ccf69c6062b259203866ef906d6eef180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 23 Jul 2025 09:30:59 +0800 Subject: [PATCH 12/41] JsonSchema: improve definitions of `colors` and `conditions` [ci skip] Fix #1862 --- doc/json_schema.json | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index b5e86a82d..2ed77d22c 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -3,7 +3,8 @@ "$defs": { "colors": { "type": "string", - "enum": [ + "description": "https://github.com/fastfetch-cli/fastfetch/wiki/Color-Format-Specification", + "examples": [ "reset_", "bright_", "dim_", "italic_", "underline_", "blink_", "inverse_", "hidden_", "strike_", "light_", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default" ] @@ -52,6 +53,7 @@ "bar-monochrome" ] }, + "uniqueItems": true, "default": [ "num", "num-color" @@ -150,13 +152,13 @@ "properties": { "system": { "description": "System name to match", - "type": "string", "oneOf": [ { "$ref": "#/$defs/systems" }, { "type": "array", + "uniqueItems": true, "items": { "$ref": "#/$defs/systems" }, @@ -170,13 +172,13 @@ }, "!system": { "description": "System name to not match", - "type": "string", "oneOf": [ { "$ref": "#/$defs/systems" }, { "type": "array", + "uniqueItems": true, "items": { "$ref": "#/$defs/systems" }, @@ -190,13 +192,13 @@ }, "arch": { "description": "Architecture to match", - "type": "string", "oneOf": [ { "$ref": "#/$defs/architectures" }, { "type": "array", + "uniqueItems": true, "items": { "$ref": "#/$defs/architectures" }, @@ -210,13 +212,13 @@ }, "!arch": { "description": "Architecture to not match", - "type": "string", "oneOf": [ { "$ref": "#/$defs/architectures" }, { "type": "array", + "uniqueItems": true, "items": { "$ref": "#/$defs/architectures" }, From 02c32549615f39ac01f0560abdbe263b524bf7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 24 Jul 2025 19:31:01 +0800 Subject: [PATCH 13/41] Netif (Windows): correct default route detection (v2) --- src/common/netif/netif.c | 3 ++- src/common/netif/netif.h | 3 --- src/common/netif/netif_windows.c | 36 +++++++++++++++++++++++++------- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/common/netif/netif.c b/src/common/netif/netif.c index da5a83859..8d02b1a54 100644 --- a/src/common/netif/netif.c +++ b/src/common/netif/netif.c @@ -3,7 +3,8 @@ #ifndef _WIN32 #include #else - #define IF_NAMESIZE 0 + #include + #include #endif bool ffNetifGetDefaultRouteImpl(char iface[IF_NAMESIZE + 1], uint32_t* ifIndex); diff --git a/src/common/netif/netif.h b/src/common/netif/netif.h index 23ca5ef60..40c97948d 100644 --- a/src/common/netif/netif.h +++ b/src/common/netif/netif.h @@ -2,8 +2,5 @@ #include "fastfetch.h" -#ifndef _WIN32 const char* ffNetifGetDefaultRouteIfName(); -#endif - uint32_t ffNetifGetDefaultRouteIfIndex(); diff --git a/src/common/netif/netif_windows.c b/src/common/netif/netif_windows.c index 54526864b..0dfd2bd07 100644 --- a/src/common/netif/netif_windows.c +++ b/src/common/netif/netif_windows.c @@ -4,31 +4,51 @@ #include // AF_INET6, IN6_IS_ADDR_UNSPECIFIED #include -bool ffNetifGetDefaultRouteImpl(FF_MAYBE_UNUSED char iface[IF_NAMESIZE + 1], uint32_t* ifIndex) +bool ffNetifGetDefaultRouteImpl(char iface[IF_NAMESIZE + 1], uint32_t* ifIndex) { PMIB_IPFORWARD_TABLE2 pIpForwardTable = NULL; - DWORD result = GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable); - if (result != NO_ERROR) + if (!NETIO_SUCCESS(GetIpForwardTable2(AF_UNSPEC, &pIpForwardTable))) return false; bool foundDefault = false; + uint32_t smallestMetric = UINT32_MAX; for (ULONG i = 0; i < pIpForwardTable->NumEntries; ++i) { MIB_IPFORWARD_ROW2* row = &pIpForwardTable->Table[i]; - if (row->Age > row->ValidLifetime) continue; - if ((row->DestinationPrefix.PrefixLength == 0) && ((row->DestinationPrefix.Prefix.Ipv4.sin_family == AF_INET && row->DestinationPrefix.Prefix.Ipv4.sin_addr.S_un.S_addr == 0) || (row->DestinationPrefix.Prefix.Ipv6.sin6_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&row->DestinationPrefix.Prefix.Ipv6.sin6_addr)))) { - *ifIndex = row->InterfaceIndex; - foundDefault = true; - break; + MIB_IF_ROW2 ifRow = { + .InterfaceIndex = row->InterfaceIndex, + }; + if (NETIO_SUCCESS(GetIfEntry2(&ifRow)) && + ifRow.OperStatus == IfOperStatusUp) + { + MIB_IPINTERFACE_ROW ipInterfaceRow = { + .Family = (row->DestinationPrefix.Prefix.Ipv4.sin_family == AF_INET) ? AF_INET : AF_INET6, + .InterfaceIndex = row->InterfaceIndex, + }; + + uint32_t realMetric = row->Metric /* Metric offset */; + + if (NETIO_SUCCESS(GetIpInterfaceEntry(&ipInterfaceRow))) + realMetric += ipInterfaceRow.Metric /* Interface metric */; + + if (realMetric < smallestMetric) + { + smallestMetric = realMetric; + *ifIndex = row->InterfaceIndex; + if (WideCharToMultiByte(CP_UTF8, 0, ifRow.Alias, -1, iface, IF_NAMESIZE, NULL, NULL) > 0) + iface[IF_NAMESIZE] = '\0'; + foundDefault = true; + } + } } } From e7a6b841071daf07fb54ff3a77de7e968c645247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 24 Jul 2025 19:31:31 +0800 Subject: [PATCH 14/41] LocalIP (Windows): ignore stopped interfaces --- src/detection/localip/localip_windows.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/detection/localip/localip_windows.c b/src/detection/localip/localip_windows.c index 7d5091699..8f10e76b4 100644 --- a/src/detection/localip/localip_windows.c +++ b/src/detection/localip/localip_windows.c @@ -95,6 +95,9 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) // Iterate through all of the adapters for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; adapter; adapter = adapter->Next) { + if (adapter->OperStatus != IfOperStatusUp) + continue; + bool isDefaultRoute = adapter->IfIndex == defaultRouteIfIndex; if ((options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT) && !isDefaultRoute) continue; From f0fc38bcb5f3ed2707d120a8dc19abd98446c8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 24 Jul 2025 20:10:54 +0800 Subject: [PATCH 15/41] LocalIP (Windows): fix link speed calcuation --- src/detection/localip/localip.h | 2 +- src/detection/localip/localip_windows.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/detection/localip/localip.h b/src/detection/localip/localip.h index 11ac50e55..a3fde0f1c 100644 --- a/src/detection/localip/localip.h +++ b/src/detection/localip/localip.h @@ -10,7 +10,7 @@ typedef struct FFLocalIpResult FFstrbuf mac; FFstrbuf flags; int32_t mtu; - int32_t speed; + int32_t speed; // in Mbps bool defaultRoute; } FFLocalIpResult; diff --git a/src/detection/localip/localip_windows.c b/src/detection/localip/localip_windows.c index 8f10e76b4..1b067885d 100644 --- a/src/detection/localip/localip_windows.c +++ b/src/detection/localip/localip_windows.c @@ -173,7 +173,7 @@ const char* ffDetectLocalIps(const FFLocalIpOptions* options, FFlist* results) { FFLocalIpResult* result = FF_LIST_GET(FFLocalIpResult, *results, results->length - 1); if (options->showType & FF_LOCALIP_TYPE_SPEED_BIT) - result->speed = (int32_t) (adapter->ReceiveLinkSpeed / 1000); + result->speed = (int32_t) (adapter->ReceiveLinkSpeed / 1000000); if (options->showType & FF_LOCALIP_TYPE_MTU_BIT) result->mtu = (int32_t) adapter->Mtu; if (options->showType & FF_LOCALIP_TYPE_FLAGS_BIT) From 316409ecc386d4b38e9018f6f289022ebccaffa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 24 Jul 2025 20:12:48 +0800 Subject: [PATCH 16/41] LocalIP: cleanup printing code --- src/modules/localip/localip.c | 105 ++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/src/modules/localip/localip.c b/src/modules/localip/localip.c index e143fcf3f..4c98b1d6f 100644 --- a/src/modules/localip/localip.c +++ b/src/modules/localip/localip.c @@ -33,57 +33,70 @@ static void formatKey(const FFLocalIpOptions* options, FFLocalIpResult* ip, uint } } -static void printIp(FFLocalIpResult* ip, bool markDefaultRoute) +static void appendSpeed(FFLocalIpResult* ip, FFstrbuf* strbuf) +{ + if (ip->speed >= 1000000) + { + if (instance.config.display.fractionNdigits >= 0) + ffStrbufAppendF(strbuf, "%.*f Tbps", instance.config.display.fractionNdigits, ip->speed / 1000000.0); + else + ffStrbufAppendF(strbuf, "%g Tbps", ip->speed / 1000000.0); + } + else if (ip->speed >= 1000) + { + if (instance.config.display.fractionNdigits >= 0) + ffStrbufAppendF(strbuf, "%.*f Gbps", instance.config.display.fractionNdigits, ip->speed / 1000.0); + else + ffStrbufAppendF(strbuf, "%g Gbps", ip->speed / 1000.0); + } + else + ffStrbufAppendF(strbuf, "%u Mbps", (unsigned) ip->speed); +} + +static void printIp(FFLocalIpResult* ip, bool markDefaultRoute, FFstrbuf* buffer) { - bool flag = false; if (ip->ipv4.length) { - ffStrbufWriteTo(&ip->ipv4, stdout); - flag = true; + ffStrbufAppend(buffer, &ip->ipv4); } if (ip->ipv6.length) { - if (flag) putchar(' '); - ffStrbufWriteTo(&ip->ipv6, stdout); - flag = true; + if (buffer->length) ffStrbufAppendC(buffer, ' '); + ffStrbufAppend(buffer, &ip->ipv6); } if (ip->mac.length) { - if (flag) - printf(" (%s)", ip->mac.chars); + if (buffer->length) + ffStrbufAppendF(buffer, " (%s)", ip->mac.chars); else - ffStrbufWriteTo(&ip->mac, stdout); - flag = true; + ffStrbufAppend(buffer, &ip->mac); } if (ip->mtu > 0 || ip->speed > 0) { + bool flag = buffer->length > 0; if (flag) - fputs(" [", stdout); + ffStrbufAppendS(buffer, " ["); if (ip->speed > 0) { - if (ip->speed >= 1000000) - printf("%g Tbps", ip->speed / 1000000.0); - else if (ip->speed >= 1000) - printf("Speed %g Gbps", ip->speed / 1000.0); - else - printf("Speed %u Mbps", (unsigned) ip->speed); - if (ip->mtu > 0) - fputs(" / ", stdout); + ffStrbufAppendS(buffer, "Speed "); + appendSpeed(ip, buffer); + if (ip->mtu > 0) + ffStrbufAppendS(buffer, " / MTU "); } if (ip->mtu > 0) - printf("MTU %u", (unsigned) ip->mtu); - putchar(']'); - flag = true; + ffStrbufAppendF(buffer, "%u", (unsigned) ip->mtu); + if (flag) + ffStrbufAppendC(buffer, ']'); } - if (ip->flags.length) { - if (flag) fputs(" <", stdout); - ffStrbufWriteTo(&ip->flags, stdout); - putchar('>'); - flag = true; + if (ip->flags.length) + { + if (buffer->length) ffStrbufAppendS(buffer, " <"); + ffStrbufAppend(buffer, &ip->flags); + ffStrbufAppendC(buffer, '>'); } - if (markDefaultRoute && flag && ip->defaultRoute) - fputs(" *", stdout); + if (markDefaultRoute && ip->defaultRoute) + ffStrbufAppendS(buffer, " *"); } void ffPrintLocalIp(FFLocalIpOptions* options) @@ -106,21 +119,20 @@ void ffPrintLocalIp(FFLocalIpOptions* options) ffListSort(&results, (const void*) sortIps); + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + if (options->showType & FF_LOCALIP_TYPE_COMPACT_BIT) { ffPrintLogoAndKey(FF_LOCALIP_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); - bool flag = false; - FF_LIST_FOR_EACH(FFLocalIpResult, ip, results) { - if (flag) - fputs(" - ", stdout); - else - flag = true; - printIp(ip, false); + if (buffer.length) + ffStrbufAppendS(&buffer, " - "); + printIp(ip, false, &buffer); } - putchar('\n'); + ffStrbufPutTo(&buffer, stdout); + ffStrbufClear(&buffer); } else { @@ -133,21 +145,13 @@ void ffPrintLocalIp(FFLocalIpOptions* options) if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); - printIp(ip, !(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT)); - putchar('\n'); + printIp(ip, !(options->showType & FF_LOCALIP_TYPE_DEFAULT_ROUTE_ONLY_BIT), &buffer); + ffStrbufPutTo(&buffer, stdout); } else { - FF_STRBUF_AUTO_DESTROY speedStr = ffStrbufCreate(); if (ip->speed > 0) - { - if (ip->speed >= 1000000) - ffStrbufSetF(&speedStr, "%g Tbps", ip->speed / 1000000.0); - else if (ip->speed >= 1000) - ffStrbufSetF(&speedStr, "%g Gbps", ip->speed / 1000.0); - else - ffStrbufSetF(&speedStr, "%u Mbps", (unsigned) ip->speed); - } + appendSpeed(ip, &buffer); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_FORMAT_ARG(ip->ipv4, "ipv4"), FF_FORMAT_ARG(ip->ipv6, "ipv6"), @@ -155,11 +159,12 @@ void ffPrintLocalIp(FFLocalIpOptions* options) FF_FORMAT_ARG(ip->name, "ifname"), FF_FORMAT_ARG(ip->defaultRoute, "is-default-route"), FF_FORMAT_ARG(ip->mtu, "mtu"), - FF_FORMAT_ARG(speedStr, "speed"), + FF_FORMAT_ARG(buffer, "speed"), FF_FORMAT_ARG(ip->flags, "flags"), })); } ++index; + ffStrbufClear(&buffer); } } From 2d1ae1b4c5850f3f348eedcac09975dfdff2821f Mon Sep 17 00:00:00 2001 From: Darloli <96697399+SorYoshino@users.noreply.github.com> Date: Fri, 25 Jul 2025 22:08:19 +0800 Subject: [PATCH 17/41] Users: correct the variable name 'session' to 'session-name' --- doc/json_schema.json | 2 +- src/modules/users/users.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index 2ed77d22c..4653d0fb9 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -475,7 +475,7 @@ "type": "string" }, "usersFormat": { - "description": "Output format of the module `Users`. See Wiki for formatting syntax\n 1. {name}: User name\n 2. {host-name}: Host name\n 3. {session}: Session name\n 4. {client-ip}: Client IP\n 5. {login-time}: Login Time in local timezone\n 6. {days}: Days after login\n 7. {hours}: Hours after login\n 8. {minutes}: Minutes after login\n 9. {seconds}: Seconds after login\n 10. {milliseconds}: Milliseconds after login\n 11. {years}: Years integer after login\n 12. {days-of-year}: Days of year after login\n 13. {years-fraction}: Years fraction after login", + "description": "Output format of the module `Users`. See Wiki for formatting syntax\n 1. {name}: User name\n 2. {host-name}: Host name\n 3. {session-name}: Session name\n 4. {client-ip}: Client IP\n 5. {login-time}: Login Time in local timezone\n 6. {days}: Days after login\n 7. {hours}: Hours after login\n 8. {minutes}: Minutes after login\n 9. {seconds}: Seconds after login\n 10. {milliseconds}: Milliseconds after login\n 11. {years}: Years integer after login\n 12. {days-of-year}: Days of year after login\n 13. {years-fraction}: Years fraction after login", "type": "string" }, "versionFormat": { diff --git a/src/modules/users/users.c b/src/modules/users/users.c index abea30aa0..9ba72977b 100644 --- a/src/modules/users/users.c +++ b/src/modules/users/users.c @@ -219,7 +219,7 @@ static FFModuleBaseInfo ffModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"User name", "name"}, {"Host name", "host-name"}, - {"Session name", "session"}, + {"Session name", "session-name"}, {"Client IP", "client-ip"}, {"Login Time in local timezone", "login-time"}, {"Days after login", "days"}, From d2f2c76dfc5e122f86e56ce6f68551aa621e9d2b Mon Sep 17 00:00:00 2001 From: Darloli <96697399+SorYoshino@users.noreply.github.com> Date: Fri, 25 Jul 2025 22:09:35 +0800 Subject: [PATCH 18/41] Version: correct the variable name 'name' to 'project-name' --- doc/json_schema.json | 2 +- src/modules/version/version.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index 4653d0fb9..32ec40e40 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -479,7 +479,7 @@ "type": "string" }, "versionFormat": { - "description": "Output format of the module `Version`. See Wiki for formatting syntax\n 1. {name}: Project name\n 2. {version}: Version\n 3. {version-tweak}: Version tweak\n 4. {build-type}: Build type (debug or release)\n 5. {sysname}: System name\n 6. {arch}: Architecture\n 7. {cmake-built-type}: CMake build type when compiling (Debug, Release, RelWithDebInfo, MinSizeRel)\n 8. {compile-time}: Date time when compiling\n 9. {compiler}: Compiler used when compiling\n 10. {libc}: Libc used when compiling", + "description": "Output format of the module `Version`. See Wiki for formatting syntax\n 1. {project-name}: Project name\n 2. {version}: Version\n 3. {version-tweak}: Version tweak\n 4. {build-type}: Build type (debug or release)\n 5. {sysname}: System name\n 6. {arch}: Architecture\n 7. {cmake-built-type}: CMake build type when compiling (Debug, Release, RelWithDebInfo, MinSizeRel)\n 8. {compile-time}: Date time when compiling\n 9. {compiler}: Compiler used when compiling\n 10. {libc}: Libc used when compiling", "type": "string" }, "vulkanFormat": { diff --git a/src/modules/version/version.c b/src/modules/version/version.c index 39a851e32..426cbc3d2 100644 --- a/src/modules/version/version.c +++ b/src/modules/version/version.c @@ -120,7 +120,7 @@ static FFModuleBaseInfo ffModuleInfo = { .generateJsonResult = (void*) ffGenerateVersionJsonResult, .generateJsonConfig = (void*) ffGenerateVersionJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { - {"Project name", "name"}, + {"Project name", "project-name"}, {"Version", "version"}, {"Version tweak", "version-tweak"}, {"Build type (debug or release)", "build-type"}, From a3182c31cb2bb56a530a1107326f569005c3480e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 25 Jul 2025 14:53:04 +0800 Subject: [PATCH 19/41] Chore: add FF_C_NONNULL --- src/common/io/io.h | 20 +++++++++++++++++++- src/util/FFcheckmacros.h | 20 ++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/common/io/io.h b/src/common/io/io.h index 4eabaed04..c783e5323 100644 --- a/src/common/io/io.h +++ b/src/common/io/io.h @@ -31,6 +31,7 @@ static inline FFNativeFD FFUnixFD2NativeFD(int unixfd) #endif } +FF_C_NONNULL(3) static inline bool ffWriteFDData(FFNativeFD fd, size_t dataSize, const void* data) { #ifndef _WIN32 @@ -41,18 +42,22 @@ static inline bool ffWriteFDData(FFNativeFD fd, size_t dataSize, const void* dat #endif } +FF_C_NONNULL(2) static inline bool ffWriteFDBuffer(FFNativeFD fd, const FFstrbuf* content) { return ffWriteFDData(fd, content->length, content->chars); } +FF_C_NONNULL(1, 3) bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data); +FF_C_NONNULL(1, 2) static inline bool ffWriteFileBuffer(const char* fileName, const FFstrbuf* buffer) { return ffWriteFileData(fileName, buffer->length, buffer->chars); } +FF_C_NONNULL(3) static inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data) { #ifndef _WIN32 @@ -66,26 +71,32 @@ static inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data) #endif } +FF_C_NONNULL(1, 3) ssize_t ffReadFileData(const char* fileName, size_t dataSize, void* data); +FF_C_NONNULL(2, 4) ssize_t ffReadFileDataRelative(FFNativeFD dfd, const char* fileName, size_t dataSize, void* data); +FF_C_NONNULL(2) bool ffAppendFDBuffer(FFNativeFD fd, FFstrbuf* buffer); +FF_C_NONNULL(1, 2) bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer); +FF_C_NONNULL(2, 3) bool ffAppendFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer); +FF_C_NONNULL(1, 2) static inline bool ffReadFileBuffer(const char* fileName, FFstrbuf* buffer) { ffStrbufClear(buffer); return ffAppendFileBuffer(fileName, buffer); } +FF_C_NONNULL(2, 3) static inline bool ffReadFileBufferRelative(FFNativeFD dfd, const char* fileName, FFstrbuf* buffer) { ffStrbufClear(buffer); return ffAppendFileBufferRelative(dfd, fileName, buffer); } -//Bit flags, combine with | typedef enum __attribute__((__packed__)) FFPathType { FF_PATHTYPE_FILE = 1 << 0, @@ -94,6 +105,7 @@ typedef enum __attribute__((__packed__)) FFPathType FF_PATHTYPE_FORCE_UNSIGNED = UINT8_MAX, } FFPathType; +FF_C_NONNULL(1) static inline bool ffPathExists(const char* path, FFPathType pathType) { #ifdef _WIN32 @@ -136,11 +148,13 @@ static inline bool ffPathExists(const char* path, FFPathType pathType) return false; } +FF_C_NONNULL(1, 2) bool ffPathExpandEnv(const char* in, FFstrbuf* out); #define FF_IO_TERM_RESP_WAIT_MS 100 // #554 FF_C_SCANF(3, 4) +FF_C_NONNULL(1, 3) const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...); // Not thread safe! @@ -157,6 +171,7 @@ static inline void ffUnsuppressIO(bool* suppressed) void ffListFilesRecursively(const char* path, bool pretty); +FF_C_NONNULL(1) static inline bool wrapClose(FFNativeFD* pfd) { assert(pfd); @@ -176,6 +191,7 @@ static inline bool wrapClose(FFNativeFD* pfd) } #define FF_AUTO_CLOSE_FD __attribute__((__cleanup__(wrapClose))) +FF_C_NONNULL(1) static inline bool wrapFclose(FILE** pfile) { assert(pfile); @@ -186,6 +202,7 @@ static inline bool wrapFclose(FILE** pfile) } #define FF_AUTO_CLOSE_FILE __attribute__((__cleanup__(wrapFclose))) +FF_C_NONNULL(1) #ifndef _WIN32 static inline bool wrapClosedir(DIR** pdir) { @@ -207,6 +224,7 @@ static inline bool wrapClosedir(HANDLE* pdir) #endif #define FF_AUTO_CLOSE_DIR __attribute__((__cleanup__(wrapClosedir))) +FF_C_NONNULL(1, 2, 3) static inline bool ffSearchUserConfigFile(const FFlist* configDirs, const char* fileSubpath, FFstrbuf* result) { // configDirs is a list of FFstrbufs include the trailing slash diff --git a/src/util/FFcheckmacros.h b/src/util/FFcheckmacros.h index 4a6243a72..00d34b2d3 100644 --- a/src/util/FFcheckmacros.h +++ b/src/util/FFcheckmacros.h @@ -4,22 +4,34 @@ #include #endif -#if defined(__GNUC__) || defined(__clang__) - #define FF_C_NODISCARD __attribute__((warn_unused_result)) +#if defined(__has_attribute) && __has_attribute(__warn_unused_result__) + #define FF_C_NODISCARD __attribute__((__warn_unused_result__)) #elif defined(_MSC_VER) #define FF_C_NODISCARD _Check_return_ #else #define FF_C_NODISCARD #endif -#if defined(__GNUC__) || defined(__clang__) +#if defined(__has_attribute) && __has_attribute(__format__) #define FF_C_PRINTF(formatStrIndex, argsStartIndex) __attribute__((__format__ (printf, formatStrIndex, argsStartIndex))) #else #define FF_C_PRINTF(formatStrIndex, argsStartIndex) #endif -#if defined(__GNUC__) || defined(__clang__) +#if defined(__has_attribute) && __has_attribute(__format__) #define FF_C_SCANF(formatStrIndex, argsStartIndex) __attribute__((__format__ (scanf, formatStrIndex, argsStartIndex))) #else #define FF_C_SCANF(formatStrIndex, argsStartIndex) #endif + +#if defined(__has_attribute) && __has_attribute(__nonnull__) + #define FF_C_NONNULL(argIndex, ...) __attribute__((__nonnull__(argIndex, ##__VA_ARGS__))) +#else + #define FF_C_NONNULL(argIndex, ...) +#endif + +#if defined(__has_attribute) && __has_attribute(__returns_nonnull__) + #define FF_C_RETURNS_NONNULL __attribute__((__returns_nonnull__)) +#else + #define FF_C_RETURNS_NONNULL +#endif From 0e8b9bec2d872303234878d5a679d1fdb2bd1874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 28 Jul 2025 15:32:18 +0800 Subject: [PATCH 20/41] FFstrbuf: add function `ffStrbufSubstr` --- src/util/FFstrbuf.c | 23 ++++++++++++++++++++++- src/util/FFstrbuf.h | 1 + tests/strbuf.c | 46 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/util/FFstrbuf.c b/src/util/FFstrbuf.c index 943d0de82..802b79a80 100644 --- a/src/util/FFstrbuf.c +++ b/src/util/FFstrbuf.c @@ -398,7 +398,8 @@ bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) if(strbuf->allocated == 0) { //static string - ffStrbufInitNS(strbuf, strbuf->length, strbuf->chars); + if (index < strbuf->length) + ffStrbufInitNS(strbuf, index, strbuf->chars); return true; } @@ -461,6 +462,26 @@ bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c) return true; } +bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end) +{ + if (__builtin_expect(start >= end, false)) + { + ffStrbufClear(strbuf); + return false; + } + + if (__builtin_expect(start == 0, false)) return ffStrbufSubstrBefore(strbuf, end); + if (__builtin_expect(end >= strbuf->length, false)) return ffStrbufSubstrAfter(strbuf, start - 1); + + uint32_t len = end - start; + ffStrbufEnsureFixedLengthFree(strbuf, len); // In case of static string + memmove(strbuf->chars, strbuf->chars + start, len); + + strbuf->length = len; + strbuf->chars[len] = '\0'; + return true; +} + uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c) { uint32_t result = 0; diff --git a/src/util/FFstrbuf.h b/src/util/FFstrbuf.h index d7fdab354..3d551d435 100644 --- a/src/util/FFstrbuf.h +++ b/src/util/FFstrbuf.h @@ -73,6 +73,7 @@ bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c); bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str); bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c); +bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end); FF_C_NODISCARD uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c); diff --git a/tests/strbuf.c b/tests/strbuf.c index 8d2c0131c..9781f0d75 100644 --- a/tests/strbuf.c +++ b/tests/strbuf.c @@ -665,6 +665,52 @@ int main(void) VERIFY(ffStrbufMatchSeparatedS(&strbuf, ":abc", ':') == true); } + { + ffStrbufSetStatic(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 0, 1); // start, end + VERIFY(ffStrbufEqualS(&strbuf, "a")); + + ffStrbufSetStatic(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 1, 1); + VERIFY(ffStrbufEqualS(&strbuf, "")); + + ffStrbufSetStatic(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 2, 1); + VERIFY(ffStrbufEqualS(&strbuf, "")); + + ffStrbufSetStatic(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 2, 3); + VERIFY(ffStrbufEqualS(&strbuf, "c")); + + ffStrbufSetStatic(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 0, 3); + VERIFY(ffStrbufEqualS(&strbuf, "abc")); + } + + { + ffStrbufSetS(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 0, 1); // start, end + VERIFY(ffStrbufEqualS(&strbuf, "a")); + + ffStrbufSetS(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 1, 1); + VERIFY(ffStrbufEqualS(&strbuf, "")); + + ffStrbufSetS(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 2, 1); + VERIFY(ffStrbufEqualS(&strbuf, "")); + + ffStrbufSetS(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 2, 3); + VERIFY(ffStrbufEqualS(&strbuf, "c")); + + ffStrbufSetS(&strbuf, "abc"); + ffStrbufSubstr(&strbuf, 0, 3); + VERIFY(ffStrbufEqualS(&strbuf, "abc")); + + ffStrbufDestroy(&strbuf); + } + //Success puts("\e[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } From 1ed804db5208695885c03be39701d0d4c25b2c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 28 Jul 2025 15:34:50 +0800 Subject: [PATCH 21/41] Wifi (macOS): use `sudo wdutil info` on Tahoe sudo, you know it. --- src/detection/wifi/wifi_apple.m | 212 +++++++++++++++++++++++++++++++- 1 file changed, 208 insertions(+), 4 deletions(-) diff --git a/src/detection/wifi/wifi_apple.m b/src/detection/wifi/wifi_apple.m index d6377908c..1792bb851 100644 --- a/src/detection/wifi/wifi_apple.m +++ b/src/detection/wifi/wifi_apple.m @@ -4,8 +4,19 @@ #import +static inline double rssiToSignalQuality(int rssi) +{ + return (double) (rssi >= -50 ? 100 : rssi <= -100 ? 0 : (rssi + 100) * 2); +} + static bool queryIpconfig(const char* ifName, FFstrbuf* result) { + if (@available(macOS 26.0, *)) + { + // ipconfig no longer work in Tahoe + return false; + } + return ffProcessAppendStdOut(result, (char* const[]) { "/usr/sbin/ipconfig", "getsummary", @@ -41,7 +52,186 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb return true; } -const char* ffDetectWifi(FFlist* result) +static const char* detectByWdutil(FFlist* result) +{ + FF_STRBUF_AUTO_DESTROY wdutil = ffStrbufCreate(); + + if (geteuid() != 0) + return "wdutil requires root privileges to run"; + + bool ok = ffProcessAppendStdOut(&wdutil, (char* const[]) { + "/usr/bin/wdutil", + "info", + NULL + }) == NULL; + if (!ok) return "Failed to run wdutil info command"; + + // ffStrbufSetS(&wdutil, "————————————————————————————————————————————————————————————————————\nNETWORK\n————————————————————————————————————————————————————————————————————\n Primary IPv4 : en0 (Wi-Fi / 37C2D473-40B7-4AFB-93D2-49C588580986)\n : 192.168.48.96\n Primary IPv6 : en0 (Wi-Fi / 37C2D473-40B7-4AFB-93D2-49C588580986)\n : 240a:42a3:5200:1092:18c7:87a2:c8df:ad5a\n : 240a:42a3:5200:1092:5837:b618:deb0:b60c\n DNS Addresses : 240a:42a3:5200:1092::1b\n : 192.168.48.210\n Apple : Reachable\n————————————————————————————————————————————————————————————————————\nWIFI\n————————————————————————————————————————————————————————————————————\n MAC Address : 9e:c4:0a:4b:fe:ab (hw=f4:d4:88:70:f3:39)\n Interface Name : en0\n Power : On [On]\n Op Mode : STA\n SSID : iQOO 13\n BSSID : 92:ad:bb:24:51:bb\n RSSI : -24 dBm\n CCA : 36 %\n Noise : -83 dBm\n Tx Rate : 229.0 Mbps\n Security : WPA2 Personal\n PHY Mode : 11ax\n MCS Index : 9\n Guard Interval : 800\n NSS : 2\n Channel : 2g6/20\n Country Code : CN\n Scan Cache Count : 37\n NetworkServiceID : 37C2D473-40B7-4AFB-93D2-49C588580986\n IPv4 Config Method : DHCP\n IPv4 Address : 192.168.48.96\n IPv4 Router : 192.168.48.210\n IPv6 Config Method : Automatic\n IPv6 Address : 240a:42a3:5200:1092:18c7:87a2:c8df:ad5a\n : 240a:42a3:5200:1092:5837:b618:deb0:b60c\n IPv6 Router : fe80::90ad:bbff:fe24:51bb\n DNS : 192.168.48.210\n : 240a:42a3:5200:1092::1b\n BTC Mode : Off\n Desense :\n Chain Ack : []\n BTC Profile 2.4GHz : Disabled\n BTC Profile 5GHz : Disabled\n Sniffer Supported : YES\n Supports 6e : No\n Supported Channels : 2g1/20,2g2/20,2g3/20,2g4/20,2g5/20,2g6/20,2g7/20,2g8/20,2g9/20,2g10/20,2g11/20,2g12/20,2g13/20,5g36/20,5g40/20,5g44/20,5g48/20,5g52/20,5g56/20,5g60/20,5g64/20,5g149/20,5g153/20,5g157/20,5g161/20,5g165/20,5g36/40,5g40/40,5g44/40,5g48/40,5g52/40,5g56/40,5g60/40,5g64/40,5g149/40,5g153/40,5g157/40,5g161/40,5g36/80,5g40/80,5g44/80,5g48/80,5g52/80,5g56/80,5g60/80,5g64/80,5g149/80,5g153/80,5g157/80,5g161/80\n————————————————————————————————————————————————————————————————————\nBLUETOOTH\n————————————————————————————————————————————————————————————————————\n Power : On\n Address : f4:d4:88:78:91:4a\n Discoverable : No\n Connectable : Yes\n Scanning : No\n Devices : 12 (paired=5 cloud=1 connected=0)\n\n iQOO Wireless Active\n Address : 20:18:5b:3a:fe:93\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n ERAZER N500\n Address : c1:35:45:fb:16:ac\n Paired : No\n CloudPaired : No\n Connected : No\n\n M585/M590\n Address : c9:7a:ff:82:3b:fe\n Paired : No\n CloudPaired : No\n Connected : No\n\n Xiaomi Earphones Explore\n Address : e0:08:71:78:75:0d\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n WH-1000XM5\n Address : 88:c9:e8:61:fa:76\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n ERAZER G501\n Address : d1:00:0f:08:ae:42\n Paired : No\n CloudPaired : No\n Connected : No\n\n MSI BluetoothMouse\n Address : e0:10:9c:40:73:c5\n Paired : No\n CloudPaired : No\n Connected : No\n\n Bluetooth Mouse M336/M337/M535\n Address : 34:88:5d:8f:c0:fe\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n MacBook Pro\n Address : 3c:15:c2:e5:00:b7\n Paired : No\n CloudPaired : Yes\n Connected : No\n\n\n Address : e4:57:68:8e:bb:28\n Paired : No\n CloudPaired : No\n Connected : No\n\n ERAZER N500\n Address : c1:35:45:fb:16:aa\n Paired : No\n CloudPaired : No\n Connected : No\n\n Pro Controller\n Address : 5c:52:1e:88:7e:70\n Paired : Yes\n CloudPaired : No\n Connected : No\n\n————————————————————————————————————————————————————————————————————\nAWDL\n————————————————————————————————————————————————————————————————————\n MAC Address : 46:3a:d9:c1:b0:2e (hw=46:3a:d9:c1:b0:2e)\n AirDrop Disc Mode : Everyone\n AWDL Enabled : No\n Interface Name : awdl0\n Power : On\n IPv6 Address : fe80::443a:d9ff:fec1:b02e\n Schedule State : n/a\n Channel Sequence : n/a\n Op Mode : n/a\n Real Time Mode : No\n Sync State : n/a\n Sync Params : n/a\n Master Channel : n/a\n Election Params : n/a\n————————————————————————————————————————————————————————————————————\nPOWER\n————————————————————————————————————————————————————————————————————\n Power Source : AC\n Battery Warning Level: None\n System Caps : FullWake:cpu disk net aud vid\n————————————————————————————————————————————————————————————————————\nWIFI FAULTS LAST HOUR\n————————————————————————————————————————————————————————————————————\n None\n————————————————————————————————————————————————————————————————————\nWIFI RECOVERIES LAST HOUR\n————————————————————————————————————————————————————————————————————\n None\n————————————————————————————————————————————————————————————————————\nWIFI LINK TESTS LAST HOUR\n————————————————————————————————————————————————————————————————————\n None"); + + // ... + // ———————————————————————————————————————————————————————————————————— + // WIFI + // ———————————————————————————————————————————————————————————————————— + // + // ———————————————————————————————————————————————————————————————————— + // ... + + { + // Remove unrelated lines + uint32_t start = ffStrbufFirstIndexS(&wdutil, "\nWIFI\n"); + if (start >= wdutil.length) + return "wdutil info command did not return WIFI section (1)"; + + start += 6; // Skip "\nWIFI\n" + start = ffStrbufNextIndexC(&wdutil, start, '\n'); + if (start >= wdutil.length) + return "wdutil info command did not return WIFI section (2)"; + start++; + + uint32_t end = ffStrbufNextIndexS(&wdutil, start, "\n——————————"); + + ffStrbufSubstr(&wdutil, start, end); + } + + + // `wdutil info ` returns a string like this: + // MAC Address : xx:xx:xx:xx:xx:xx (hw=xx:xx:xx:xx:xx:xx) + // Interface Name : en0 + // Power : On [On] + // Op Mode : STA + // SSID : XXX-XXX + // BSSID : xx:xx:xx:xx:xx:xx + // RSSI : -58 dBm + // CCA : 29 % + // Noise : -96 dBm + // Tx Rate : 173.0 Mbps + // Security : WPA2 Enterprise + // 802.1X Mode : User + // 802.1X Supplicant : Authenticated + // PHY Mode : 11ac + // MCS Index : 8 + // Guard Interval : 400 + // NSS : 2 + // Channel : 5g165/20 + // Country Code : CN + // Scan Cache Count : 28 + // NetworkServiceID : XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + // IPv4 Config Method : DHCP + // IPv4 Address : xx.xx.xx.xx + // IPv4 Router : xx.xx.xx.x + // IPv6 Config Method : Automatic + // IPv6 Address : xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + // IPv6 Router : None + // DNS : xxx.xxx.xxx.xxx + // : xxx.xxx.xxx.xxx + // BTC Mode : Off + // Desense : + // Chain Ack : [] + // BTC Profile 2.4GHz : Disabled + // BTC Profile 5GHz : Disabled + // Sniffer Supported : YES + // Supports 6e : No + // Supported Channels : 2g1/20,2g2/20,2g3/20,2g4/20,2g5/20,2g6/20,2g7/20,2g8/20,2g9/20,2g10/20,2g11/20,2g12/20,2g13/20,5g36/20,5g40/20,5g44/20,5g48/20,5g52/20,5g56/20,5g60/20,5g64/20,5g149/20,5g153/20,5g157/20,5g161/20,5g165/20,5g36/40,5g40/40,5g44/40,5g48/40,5g52/40,5g56/40,5g60/40,5g64/40,5g149/40,5g153/40,5g157/40,5g161/40,5g36/80,5g40/80,5g44/80,5g48/80,5g52/80,5g56/80,5g60/80,5g64/80,5g149/80,5g153/80,5g157/80,5g161/80 + + FFWifiResult* item = (FFWifiResult*) ffListAdd(result); + ffStrbufInit(&item->inf.description); + ffStrbufInit(&item->inf.status); + ffStrbufInit(&item->conn.status); + ffStrbufInit(&item->conn.ssid); + ffStrbufInit(&item->conn.bssid); + ffStrbufInit(&item->conn.protocol); + ffStrbufInit(&item->conn.security); + item->conn.signalQuality = 0.0/0.0; + item->conn.rxRate = 0.0/0.0; + item->conn.txRate = 0.0/0.0; + item->conn.channel = 0; + item->conn.frequency = 0; + + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, &wdutil)) + { + const char* key = line + 4; // Skip " " + const char* value = key + strlen("MAC Address : "); + switch (key[0] << 24 | key[1] << 16 | key[2] << 8 | key[3]) + { + case 'Inte': // Interface Name + ffStrbufAppendS(&item->inf.description, value); + break; + case 'Powe': // Power + if (ffStrStartsWith(value, "On ")) + ffStrbufSetStatic(&item->inf.status, "Power On"); + else + ffStrbufSetStatic(&item->inf.status, "Power Off"); + break; + case 'SSID': // SSID + ffStrbufAppendS(&item->conn.ssid, value); + break; + case 'BSSI': // BSSID + if (ffStrEquals(value, "None") && ffStrbufEqualS(&item->conn.ssid, "None")) + { + ffStrbufSetStatic(&item->conn.status, "Inactive"); + ffStrbufClear(&item->conn.ssid); // None + return NULL; + } + ffStrbufSetStatic(&item->conn.status, "Active"); + ffStrbufAppendS(&item->conn.bssid, value); + break; + case 'RSSI': // RSSI + item->conn.signalQuality = rssiToSignalQuality((int) strtol(value, NULL, 10)); + break; + case 'Tx R': // Tx Rate + item->conn.txRate = strtod(value, NULL); + break; + case 'Secu': // Security + if (ffStrEquals(value, "None")) + ffStrbufSetStatic(&item->conn.security, "Insecure"); + else + ffStrbufAppendS(&item->conn.security, value); + break; + case 'PHY ': // PHY Mode + if (ffStrEquals(value, "None")) + ffStrbufSetStatic(&item->conn.protocol, "none"); + else if (ffStrEquals(value, "11a")) + ffStrbufSetStatic(&item->conn.protocol, "802.11a"); + else if (ffStrEquals(value, "11b")) + ffStrbufSetStatic(&item->conn.protocol, "802.11b"); + else if (ffStrEquals(value, "11g")) + ffStrbufSetStatic(&item->conn.protocol, "802.11g"); + else if (ffStrEquals(value, "11n")) + ffStrbufSetStatic(&item->conn.protocol, "802.11n (Wi-Fi 4)"); + else if (ffStrEquals(value, "11ac")) + ffStrbufSetStatic(&item->conn.protocol, "802.11ac (Wi-Fi 5)"); + else if (ffStrEquals(value, "11ax")) + ffStrbufSetStatic(&item->conn.protocol, "802.11ax (Wi-Fi 6)"); + else if (ffStrEquals(value, "11be")) + ffStrbufSetStatic(&item->conn.protocol, "802.11be (Wi-Fi 7)"); + else + ffStrbufAppendS(&item->conn.protocol, value); + break; + case 'Chan': // Channel + { + int band, channel; + if (sscanf(value, "%dg%d", &band, &channel) == 2) + { + item->conn.channel = (uint16_t) channel; + switch (band) + { + case 2: item->conn.frequency = 2400; break; + case 5: item->conn.frequency = 5400; break; + case 6: item->conn.frequency = 6400; break; + default: item->conn.frequency = 0; break; + } + } + break; + } + } + } + + return NULL; +} + +static const char* detectByCoreWlan(FFlist* result) { NSArray* interfaces = CWWiFiClient.sharedWiFiClient.interfaces; if (!interfaces) @@ -49,7 +239,7 @@ const char* ffDetectWifi(FFlist* result) for (CWInterface* inf in interfaces) { - FFWifiResult* item = (FFWifiResult*)ffListAdd(result); + FFWifiResult* item = (FFWifiResult*) ffListAdd(result); ffStrbufInit(&item->inf.description); ffStrbufInit(&item->inf.status); ffStrbufInit(&item->conn.status); @@ -79,12 +269,14 @@ const char* ffDetectWifi(FFlist* result) else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) getWifiInfoByIpconfig(&ipconfig, "\n SSID : ", &item->conn.ssid); else - ffStrbufSetStatic(&item->conn.ssid, ""); // https://developer.apple.com/forums/thread/732431 + ffStrbufSetStatic(&item->conn.ssid, ""); // https://developer.apple.com/forums/thread/732431 if (inf.bssid) ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String); else if (ipconfig.length || (queryIpconfig(item->inf.description.chars, &ipconfig))) getWifiInfoByIpconfig(&ipconfig, "\n BSSID : ", &item->conn.bssid); + else + ffStrbufSetStatic(&item->conn.bssid, ""); switch(inf.activePHYMode) { @@ -117,7 +309,7 @@ const char* ffDetectWifi(FFlist* result) ffStrbufAppendF(&item->conn.protocol, "Unknown (%ld)", inf.activePHYMode); break; } - item->conn.signalQuality = (double) (inf.rssiValue >= -50 ? 100 : inf.rssiValue <= -100 ? 0 : (inf.rssiValue + 100) * 2); + item->conn.signalQuality = rssiToSignalQuality((int) inf.rssiValue); item->conn.txRate = inf.transmitRate; switch(inf.security) @@ -189,5 +381,17 @@ const char* ffDetectWifi(FFlist* result) default: item->conn.frequency = 0; break; } } + return NULL; } + +const char* ffDetectWifi(FFlist* result) +{ + if (@available(macOS 26.0, *)) + { + if (detectByWdutil(result) == NULL) + return NULL; + } + + return detectByCoreWlan(result); +} From d1f20822f112cf0fa485dd98d82be00f0ca3c616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 28 Jul 2025 15:39:35 +0800 Subject: [PATCH 22/41] GPU (macOS): silence compiler warnings --- src/detection/gpu/gpu_apple.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/detection/gpu/gpu_apple.m b/src/detection/gpu/gpu_apple.m index b4937fbf3..e206bdc1a 100644 --- a/src/detection/gpu/gpu_apple.m +++ b/src/detection/gpu/gpu_apple.m @@ -60,10 +60,13 @@ const char* ffGpuDetectMetal(FFlist* gpus) else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1"); #else // MAC_OS_X_VERSION_10_15 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability-new" if ([device supportsFamily:MTLGPUFamilyMetal4]) ffStrbufSetStatic(&gpu->platformApi, "Metal 4"); else if ([device supportsFamily:MTLGPUFamilyMetal3]) ffStrbufSetStatic(&gpu->platformApi, "Metal 3"); + #pragma clang diagnostic pop else if ([device supportsFamily:MTLGPUFamilyCommon3]) ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3"); else if ([device supportsFamily:MTLGPUFamilyCommon2]) From fe76bbe68a667fd5bd72bbb08070f132136c9f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 28 Jul 2025 23:06:15 +0800 Subject: [PATCH 23/41] Logo (Builtin): remove Evolinx --- src/logo/ascii/evolinx.txt | 10 ---------- src/logo/builtin.c | 10 ---------- 2 files changed, 20 deletions(-) delete mode 100644 src/logo/ascii/evolinx.txt diff --git a/src/logo/ascii/evolinx.txt b/src/logo/ascii/evolinx.txt deleted file mode 100644 index 6aa225471..000000000 --- a/src/logo/ascii/evolinx.txt +++ /dev/null @@ -1,10 +0,0 @@ -#-----------------------------------------------------------------------# -|#SSSSSSSS\*SS\****SS\**SSSSSS\**SS\*******SSSSSS\*SS\***SS\*SS\***SS\*#| -|#SS**_____|SS*|***SS*|SS**__SS\*SS*|******\_SS**_|SSS\**SS*|SS*|**SS*|#| -|#SS*|******SS*|***SS*|SS*/**SS*|SS*|********SS*|**SSSS\*SS*|\SS\*SS**|#| -|#SSSSS\****\SS\**SS**|SS*|**SS*|SS*|********SS*|**SS*SS\SS*|*\SSSS**/*#| -|#SS**__|****\SS\SS**/*SS*|**SS*|SS*|********SS*|**SS*\SSSS*|*SS**SS<**#| -|#SS*|********\SSS**/**SS*|**SS*|SS*|********SS*|**SS*|\SSS*|SS**/\SS\*#| -|#SSSSSSSS\****\S**/****SSSSSS**|SSSSSSSS\*SSSSSS\*SS*|*\SS*|SS*/**SS*|#| -|#\________|****\_/*****\______/*\________|\______|\__|**\__|\__|**\__|#| -#-----------------------------------------------------------------------# \ No newline at end of file diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 0322b1320..35eabdb65 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -1623,16 +1623,6 @@ static const FFlogo E[] = { .colorKeys = FF_COLOR_FG_BLUE, .colorTitle = FF_COLOR_FG_DEFAULT, }, - // Evolinx - { - .names = {"Evolinx"}, - .lines = FASTFETCH_DATATEXT_LOGO_EVOLINX, - .colors = { - FF_COLOR_FG_BLUE, - }, - .colorKeys = FF_COLOR_FG_BLUE, - .colorTitle = FF_COLOR_FG_BLUE, - }, // EvolutionOS { .names = {"EvolutionOS"}, From 4909d693e327d0f81b993694c3873c203c4ce732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 28 Jul 2025 23:41:48 +0800 Subject: [PATCH 24/41] Packages (Windows): don't rely on the existance of `$SCOOP` when detecting scoop packages Fix #1868 --- src/detection/packages/packages_windows.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/detection/packages/packages_windows.c b/src/detection/packages/packages_windows.c index a9fa9df6d..1d13e0798 100644 --- a/src/detection/packages/packages_windows.c +++ b/src/detection/packages/packages_windows.c @@ -1,6 +1,7 @@ #include "packages.h" #include "common/processing.h" #include "util/stringUtils.h" +#include "util/path.h" #include #include @@ -37,10 +38,11 @@ static void detectScoop(FFPackagesResult* result) { FF_STRBUF_AUTO_DESTROY scoopPath = ffStrbufCreateA(MAX_PATH + 3); - const char* scoopEnv = getenv("SCOOP"); - if(ffStrSet(scoopEnv)) + if(ffFindExecutableInPath("scoop.cmd", &scoopPath) == NULL) { - ffStrbufAppendS(&scoopPath, scoopEnv); + // C:\Users\$USER\scoop\shims\scoop.cmd + ffStrbufSubstrBeforeLastC(&scoopPath, '\\'); + ffStrbufSubstrBeforeLastC(&scoopPath, '\\'); ffStrbufAppendS(&scoopPath, "/apps/*"); } else From 71ddc4ffc65f863d61c0662e5f1f0d8c72a48b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 28 Jul 2025 23:49:27 +0800 Subject: [PATCH 25/41] Disk: update default value of `hideFolders` Fix #1869 --- doc/json_schema.json | 3 ++- src/data/help.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index 32ec40e40..80924361c 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -1961,7 +1961,8 @@ }, "hideFolders": { "type": "string", - "description": "A colon (semicolon on Windows) separated list of folder paths to hide from the disk output\nDefault: /efi:/boot:/boot/efi" + "description": "A colon (semicolon on Windows) separated list of folder paths to hide from the disk output", + "default": "/efi:/boot:/boot/efi:/boot/firmware" }, "hideFS": { "type": "string", diff --git a/src/data/help.json b/src/data/help.json index dbb27f5ac..788428a23 100644 --- a/src/data/help.json +++ b/src/data/help.json @@ -854,7 +854,7 @@ "desc": "A colon (semicolon on Windows) separated list of folder paths to hide from the disk output", "arg": { "type": "path", - "default": "/efi:/boot:/boot/efi" + "default": "/efi:/boot:/boot/efi:/boot/firmware" } }, { From 8c2f93151deed0ed975702fdb78bb8c9f89b6873 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 29 Jul 2025 08:54:16 +0800 Subject: [PATCH 26/41] CPU (Android): add more QSD SOCs --- src/detection/cpu/cpu_linux.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c index 012af9202..f3c55c697 100644 --- a/src/detection/cpu/cpu_linux.c +++ b/src/detection/cpu/cpu_linux.c @@ -100,7 +100,9 @@ static void detectQualcomm(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips - if (ffStrbufEqualS(&cpu->name, "SM8750-AC")) + if (ffStrbufEqualS(&cpu->name, "SM8735")) + ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8s Gen 4 [SM8735]"); + else if (ffStrbufEqualS(&cpu->name, "SM8750-AC")) ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Elite for Galaxy [SM8750-AC]"); else if (ffStrbufEqualS(&cpu->name, "SM8750-3")) ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Elite [SM8750-3]"); @@ -121,6 +123,8 @@ static void detectQualcomm(FFCPUResult* cpu) else if (ffStrbufEqualS(&cpu->name, "SM8450")) ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Gen 1 [SM8450]"); + else if (ffStrbufEqualS(&cpu->name, "SM7750")) + ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7 Gen 4 [SM7750]"); else if (ffStrbufEqualS(&cpu->name, "SM7675")) ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7+ Gen 3 [SM7675]"); else if (ffStrbufEqualS(&cpu->name, "SM7635")) @@ -134,6 +138,8 @@ static void detectQualcomm(FFCPUResult* cpu) else if (ffStrbufEqualS(&cpu->name, "SM7450")) ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7 Gen 1 [SM7450]"); + else if (ffStrbufEqualS(&cpu->name, "SM6650")) + ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6 Gen 4 [SM6650]"); else if (ffStrbufEqualS(&cpu->name, "SM6375-AC")) ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6s Gen 3 [SM6375-AC]"); else if (ffStrbufEqualS(&cpu->name, "SM6475")) From 6ec73863abc9b8c211e133f0428259f04add390c Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 29 Jul 2025 09:24:54 +0800 Subject: [PATCH 27/41] CPU (Android): make SOC detection more loose It's less accurate, but higher chance to match. --- src/detection/cpu/cpu_linux.c | 138 +++++++++++++++------------------- 1 file changed, 61 insertions(+), 77 deletions(-) diff --git a/src/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c index f3c55c697..46ab37b78 100644 --- a/src/detection/cpu/cpu_linux.c +++ b/src/detection/cpu/cpu_linux.c @@ -100,91 +100,75 @@ static void detectQualcomm(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips - if (ffStrbufEqualS(&cpu->name, "SM8735")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8s Gen 4 [SM8735]"); - else if (ffStrbufEqualS(&cpu->name, "SM8750-AC")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Elite for Galaxy [SM8750-AC]"); - else if (ffStrbufEqualS(&cpu->name, "SM8750-3")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Elite [SM8750-3]"); - else if (ffStrbufEqualS(&cpu->name, "SM8750")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Elite [SM8750]"); - else if (ffStrbufEqualS(&cpu->name, "SM8635")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8s Gen 3 [SM8635]"); - else if (ffStrbufEqualS(&cpu->name, "SM8650-AC")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Gen 3 for Galaxy [SM8650-AC]"); - else if (ffStrbufEqualS(&cpu->name, "SM8650")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Gen 3 [SM8650]"); - else if (ffStrbufEqualS(&cpu->name, "SM8550-AC")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Gen 2 for Galaxy [SM8550-AC]"); - else if (ffStrbufEqualS(&cpu->name, "SM8550")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Gen 2 [SM8550]"); - else if (ffStrbufEqualS(&cpu->name, "SM8475")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8+ Gen 1 [SM8475]"); - else if (ffStrbufEqualS(&cpu->name, "SM8450")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 8 Gen 1 [SM8450]"); + uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10); + const char* name = NULL; - else if (ffStrbufEqualS(&cpu->name, "SM7750")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7 Gen 4 [SM7750]"); - else if (ffStrbufEqualS(&cpu->name, "SM7675")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7+ Gen 3 [SM7675]"); - else if (ffStrbufEqualS(&cpu->name, "SM7635")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7s Gen 3 [SM7635]"); - else if (ffStrbufEqualS(&cpu->name, "SM7550")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7 Gen 3 [SM7550]"); - else if (ffStrbufEqualS(&cpu->name, "SM7475")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7+ Gen 2 [SM7550]"); - else if (ffStrbufEqualS(&cpu->name, "SM7435")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7s Gen 2 [SM7435]"); - else if (ffStrbufEqualS(&cpu->name, "SM7450")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 7 Gen 1 [SM7450]"); + switch (code) + { + case 8735: name = "8s Gen 4"; break; + case 8750: name = "8 Elite"; break; + case 8635: name = "8s Gen 3"; break; + case 8650: name = "8 Gen 3"; break; + case 8550: name = "8 Gen 2"; break; + case 8475: name = "8+ Gen 1"; break; + case 8450: name = "8 Gen 1"; break; + case 7750: name = "7 Gen 4"; break; + case 7675: name = "7+ Gen 3"; break; + case 7635: name = "7s Gen 3"; break; + case 7550: name = "7 Gen 3"; break; + case 7475: name = "7+ Gen 2"; break; + case 7435: name = "7s Gen 2"; break; + case 7450: name = "7 Gen 1"; break; + case 6650: name = "6 Gen 4"; break; + case 6375: name = "6s Gen 3"; break; + case 6475: name = "6 Gen 3"; break; + case 6115: name = "6s Gen 1"; break; + case 6450: name = "6 Gen 1"; break; + case 4635: name = "4s Gen 2"; break; + case 4450: name = "4 Gen 2"; break; + case 4375: name = "4 Gen 1"; break; + } - else if (ffStrbufEqualS(&cpu->name, "SM6650")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6 Gen 4 [SM6650]"); - else if (ffStrbufEqualS(&cpu->name, "SM6375-AC")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6s Gen 3 [SM6375-AC]"); - else if (ffStrbufEqualS(&cpu->name, "SM6475")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6 Gen 3 [SM6475]"); - else if (ffStrbufEqualS(&cpu->name, "SM6115")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6s Gen 1 [SM6115]"); - else if (ffStrbufEqualS(&cpu->name, "SM6450")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 6 Gen 1 [SM6450]"); - - else if (ffStrbufEqualS(&cpu->name, "SM4635")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 4s Gen 2 [SM4635]"); - else if (ffStrbufEqualS(&cpu->name, "SM4450")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 4 Gen 2 [SM4450]"); - else if (ffStrbufEqualS(&cpu->name, "SM4375")) - ffStrbufSetStatic(&cpu->name, "Qualcomm Snapdragon 4 Gen 1 [SM4375]"); + if (name) + { + char str[32]; + ffStrCopy(str, cpu->name.chars, sizeof(str)); + ffStrbufSetF(&cpu->name, "Qualcomm Snapdragon %s [%s]", name, str); + return; + } } static void detectMediaTek(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/List_of_MediaTek_systems_on_chips - if (ffStrbufEqualS(&cpu->name, "MT6991")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9400 [MT6991]"); - else if (ffStrbufEqualS(&cpu->name, "MT6991Z")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9400 [MT6991Z]"); - else if (ffStrbufEqualS(&cpu->name, "MT6989Z")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9300+ [MT6989Z]"); - else if (ffStrbufEqualS(&cpu->name, "MT8796Z")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9300+ [MT8796Z]"); - else if (ffStrbufEqualS(&cpu->name, "MT6989")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9300 [MT6989]"); - else if (ffStrbufEqualS(&cpu->name, "MT8796")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9300 [MT8796]"); - else if (ffStrbufEqualS(&cpu->name, "MT6985W")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9200+ [MT6985W]"); - else if (ffStrbufEqualS(&cpu->name, "MT6985")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9200 [MT6985]"); - else if (ffStrbufEqualS(&cpu->name, "MT6983W")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9000+ [MT6983W]"); - else if (ffStrbufEqualS(&cpu->name, "MT8798Z/T")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9000+ [MT8798Z/T]"); - else if (ffStrbufEqualS(&cpu->name, "MT6983Z")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9000 [MT6983Z]"); - else if (ffStrbufEqualS(&cpu->name, "MT8798Z/C")) - ffStrbufSetStatic(&cpu->name, "MediaTek Dimensity 9000 [MT8798Z/C]"); + uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10); + const char* name = NULL; + + switch (code) // The SOC code of MTK Dimensity series is full of mess + { + case 6991: name = "9400"; break; + case 6989: + case 8796: name = "9300"; break; + case 6985: name = "9200"; break; + case 6983: + case 8798: name = "9000"; break; + + case 6899: name = "8400"; break; + case 6897: + case 8792: name = "8300"; break; + case 6896: name = "8200"; break; + case 8795: name = "8100"; break; + case 6895: name = "8000"; break; + } + + if (name) + { + char str[32]; + ffStrCopy(str, cpu->name.chars, sizeof(str)); + ffStrbufSetF(&cpu->name, "MediaTek Dimensity %s [%s]", name, str); + return; + } } static void detectAndroid(FFCPUResult* cpu) From 9ea9291ab841da84743b2425ca74c75591000285 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 29 Jul 2025 10:20:05 +0800 Subject: [PATCH 28/41] WM (Linux): add labwc version detection support --- src/detection/wm/wm_linux.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/detection/wm/wm_linux.c b/src/detection/wm/wm_linux.c index 9b9956840..4743bc252 100644 --- a/src/detection/wm/wm_linux.c +++ b/src/detection/wm/wm_linux.c @@ -255,6 +255,29 @@ static const char* getOpenbox(FFstrbuf* result) return "Failed to run command `openbox --version`"; } +static const char* getLabwc(FFstrbuf* result) +{ + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); + const char* error = ffFindExecutableInPath("labwc", &path); + if (error) return "Failed to find labwc executable path"; + + ffBinaryExtractStrings(path.chars, extractCommonWmVersion, result, (uint32_t) strlen("0.0.0")); + if (result->length > 0) return NULL; + + if (ffProcessAppendStdOut(result, (char* const[]){ + path.chars, + "--version", + NULL + }) == NULL) + { // labwc 0.9.0 (+xwayland +nls +rsvg +libsfdo) + ffStrbufSubstrAfterFirstC(result, ' '); + ffStrbufSubstrBeforeFirstC(result, ' '); + return NULL; + } + + return "Failed to run command `openbox --version`"; +} + const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options) { if (!wmName) @@ -281,5 +304,8 @@ const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE if (ffStrbufEqualS(wmName, "Openbox")) return getOpenbox(result); + if (ffStrbufEqualS(wmName, "labwc")) + return getLabwc(result); + return "Unsupported WM"; } From 5bb6e226798b4d6fba38af87b7aee7081b9b69cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 29 Jul 2025 14:17:29 +0800 Subject: [PATCH 29/41] GPU (Intel): read render engine temperature only --- src/detection/gpu/gpu_intel.c | 21 +++++++++++---------- src/detection/gpu/igcl.h | 24 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/detection/gpu/gpu_intel.c b/src/detection/gpu/gpu_intel.c index 59f268930..f5fe08cd5 100644 --- a/src/detection/gpu/gpu_intel.c +++ b/src/detection/gpu/gpu_intel.c @@ -10,7 +10,7 @@ struct FFIgclData { FF_LIBRARY_SYMBOL(ctlEnumerateDevices) FF_LIBRARY_SYMBOL(ctlGetDeviceProperties) FF_LIBRARY_SYMBOL(ctlEnumTemperatureSensors) - FF_LIBRARY_SYMBOL(ctlTemperatureGetState) + FF_LIBRARY_SYMBOL(ctlTemperatureGetProperties) FF_LIBRARY_SYMBOL(ctlEnumMemoryModules) FF_LIBRARY_SYMBOL(ctlMemoryGetProperties) FF_LIBRARY_SYMBOL(ctlMemoryGetState) @@ -41,7 +41,7 @@ const char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverRe FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumerateDevices) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlGetDeviceProperties) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumTemperatureSensors) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlTemperatureGetState) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlTemperatureGetProperties) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumMemoryModules) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetProperties) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetState) @@ -204,19 +204,20 @@ const char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverRe uint32_t sensorCount = ARRAY_SIZE(sensors); if (igclData.ffctlEnumTemperatureSensors(device, &sensorCount, sensors) == CTL_RESULT_SUCCESS && sensorCount > 0) { - double sumValue = 0; - uint32_t availableCount = 0; for (uint32_t iSensor = 0; iSensor < sensorCount; iSensor++) { - double value; - if (igclData.ffctlTemperatureGetState(sensors[iSensor], &value) == CTL_RESULT_SUCCESS) + ctl_temp_properties_t props = { .Size = sizeof(props) }; + // The official sample code does not set Version + // https://github.com/intel/drivers.gpu.control-library/blob/1bbacbf3814f2fd0d2b930cdf42fad83f3628db9/Samples/Telemetry_Samples/Sample_TelemetryAPP.cpp#L256 + if (igclData.ffctlTemperatureGetProperties(sensors[iSensor], &props) == CTL_RESULT_SUCCESS) { - sumValue += value; - availableCount++; + if (props.type == CTL_TEMP_SENSORS_GPU) + { + *result.temp = props.maxTemperature; + break; + } } } - if (availableCount > 0) - *result.temp = sumValue / availableCount; } } diff --git a/src/detection/gpu/igcl.h b/src/detection/gpu/igcl.h index 60e5cf22a..8e8ff6f96 100644 --- a/src/detection/gpu/igcl.h +++ b/src/detection/gpu/igcl.h @@ -120,8 +120,28 @@ typedef struct ctl_temp_handle_t* ctl_temp_handle_t; // https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumtemperaturesensors extern ctl_result_t ctlEnumTemperatureSensors(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_temp_handle_t* phTemperature); -// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctltemperaturegetstate -extern ctl_result_t ctlTemperatureGetState(ctl_temp_handle_t hTemperature, double* pTemperature); +// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv427ctlTemperatureGetProperties17ctl_temp_handle_tP21ctl_temp_properties_t + +typedef enum ctl_temp_sensors_t +{ + CTL_TEMP_SENSORS_GLOBAL = 0, + CTL_TEMP_SENSORS_GPU = 1, + CTL_TEMP_SENSORS_MEMORY = 2, + CTL_TEMP_SENSORS_GLOBAL_MIN = 3, + CTL_TEMP_SENSORS_GPU_MIN = 4, + CTL_TEMP_SENSORS_MEMORY_MIN = 5, + CTL_TEMP_SENSORS_MAX +} ctl_temp_sensors_t; + +typedef struct _ctl_temp_properties_t +{ + uint32_t Size; + uint8_t Version; + ctl_temp_sensors_t type; + double maxTemperature; +} ctl_temp_properties_t; + +extern ctl_result_t ctlTemperatureGetProperties(ctl_temp_handle_t hTemperature, ctl_temp_properties_t* pTemperature); // https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t typedef struct ctl_mem_handle_t* ctl_mem_handle_t; From 77dd18466b474da27804196e1dbfd6acaae4b1f9 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 29 Jul 2025 16:36:21 +0800 Subject: [PATCH 30/41] FFstrbuf: add function `ffStrbufAppendUtf32CodePoint` --- src/util/FFstrbuf.c | 32 ++++++++++++++++++++++++++++++++ src/util/FFstrbuf.h | 2 ++ tests/strbuf.c | 9 +++++++++ 3 files changed, 43 insertions(+) diff --git a/src/util/FFstrbuf.c b/src/util/FFstrbuf.c index 802b79a80..6fe0b151c 100644 --- a/src/util/FFstrbuf.c +++ b/src/util/FFstrbuf.c @@ -682,3 +682,35 @@ bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const return false; } + +int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint) +{ + if (codepoint <= 0x7F) { + ffStrbufAppendC(strbuf, (char)codepoint); + return 1; + } else if (codepoint <= 0x7FF) { + ffStrbufAppendNS(strbuf, 2, (char[]){ + (char) (0xC0 | (codepoint >> 6)), + (char) (0x80 | (codepoint & 0x3F)) + }); + return 2; + } else if (codepoint <= 0xFFFF) { + ffStrbufAppendNS(strbuf, 3, (char[]){ + (char) (0xE0 | (codepoint >> 12)), + (char) (0x80 | ((codepoint >> 6) & 0x3F)), + (char) (0x80 | (codepoint & 0x3F)) + }); + return 3; + } else if (codepoint <= 0x10FFFF) { + ffStrbufAppendNS(strbuf, 4, (char[]){ + (char) (0xF0 | (codepoint >> 18)), + (char) (0x80 | ((codepoint >> 12) & 0x3F)), + (char) (0x80 | ((codepoint >> 6) & 0x3F)), + (char) (0x80 | (codepoint & 0x3F)) + }); + return 4; + } + + ffStrbufAppendS(strbuf, "�"); // U+FFFD REPLACEMENT CHARACTER + return 1; +} diff --git a/src/util/FFstrbuf.h b/src/util/FFstrbuf.h index 3d551d435..7e9692722 100644 --- a/src/util/FFstrbuf.h +++ b/src/util/FFstrbuf.h @@ -96,6 +96,8 @@ void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer); bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf); bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); +int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint); + FF_C_NODISCARD static inline FFstrbuf ffStrbufCreateA(uint32_t allocate) { FFstrbuf strbuf; diff --git a/tests/strbuf.c b/tests/strbuf.c index 9781f0d75..94addc466 100644 --- a/tests/strbuf.c +++ b/tests/strbuf.c @@ -711,6 +711,15 @@ int main(void) ffStrbufDestroy(&strbuf); } + { + ffStrbufAppendUtf32CodePoint(&strbuf, 0x6587); + ffStrbufAppendUtf32CodePoint(&strbuf, 0x6cc9); + ffStrbufAppendUtf32CodePoint(&strbuf, 0x9a7f); + VERIFY(ffStrbufEqualS(&strbuf, u8"文泉驿")); + + ffStrbufDestroy(&strbuf); + } + //Success puts("\e[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } From b1f0248fb30794fdef5c262297300bc33c9ce4fe Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 29 Jul 2025 16:37:12 +0800 Subject: [PATCH 31/41] Font (Linux): parse unicode escaped string stored by qt5ct Fix #1864 --- src/detection/gtk_qt/qt.c | 42 +++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/detection/gtk_qt/qt.c b/src/detection/gtk_qt/qt.c index ad2c0ab2d..b27ad748f 100644 --- a/src/detection/gtk_qt/qt.c +++ b/src/detection/gtk_qt/qt.c @@ -140,18 +140,45 @@ static void detectQtCt(char qver, FFQtResult* result) // by the same author and qt6ct understands qt5ct in qt6 applications as well. char file[] = "qtXct/qtXct.conf"; file[2] = file[8] = qver; + + FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate(); + ffParsePropFileConfigValues(file, 3, (FFpropquery[]) { {"style=", &result->widgetStyle}, {"icon_theme=", &result->icons}, - {"general=", &result->font} + {"general=", &font} }); - if (ffStrbufStartsWithC(&result->font, '@')) + if (ffStrbufStartsWithC(&font, '@')) { // See QVariant notes on https://doc.qt.io/qt-5/qsettings.html and // https://github.com/fastfetch-cli/fastfetch/issues/1053#issuecomment-2197254769 // Thankfully, newer versions use the more common font encoding. - ffStrbufSetNS(&result->font, 5, file); + ffStrbufSetNS(&font, 5, file); + } + else if (qver == '5') + { + // #1864 + const char *p = font.chars; + + while (*p) + { + if (p[0] == '\\' && p[1] == 'x' && isxdigit(p[2]) && isxdigit(p[3]) && isxdigit(p[4]) && isxdigit(p[5])) + { + uint32_t codepoint = (uint32_t)strtoul((char[]) { p[2], p[3], p[4], p[5], '\0' }, NULL, 16); + ffStrbufAppendUtf32CodePoint(&result->font, codepoint); + p += 6; + } + else + { + ffStrbufAppendC(&result->font, *p++); + } + } + } + else + { + ffStrbufDestroy(&result->font); + ffStrbufInitMove(&result->font, &font); } } @@ -178,14 +205,17 @@ const FFQtResult* ffDetectQt(void) ffStrbufInit(&result.wallpaper); const FFDisplayServerResult* wmde = ffConnectDisplayServer(); - const char *qplatformtheme = getenv("QT_QPA_PLATFORMTHEME"); if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_PLASMA)) detectPlasma(&result); else if(ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_LXQT)) detectLXQt(&result); - else if(ffStrSet(qplatformtheme) && (ffStrEquals(qplatformtheme, "qt5ct") || ffStrEquals(qplatformtheme, "qt6ct"))) - detectQtCt(qplatformtheme[2], &result); + else + { + const char *qPlatformTheme = getenv("QT_QPA_PLATFORMTHEME"); + if(qPlatformTheme && (ffStrEquals(qPlatformTheme, "qt5ct") || ffStrEquals(qPlatformTheme, "qt6ct"))) + detectQtCt(qPlatformTheme[2], &result); + } if(ffStrbufEqualS(&result.widgetStyle, "kvantum") || ffStrbufEqualS(&result.widgetStyle, "kvantum-dark")) { From ca79bae36ee474cf69f053e86418b103ffaaaff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 29 Jul 2025 19:22:18 +0800 Subject: [PATCH 32/41] Packages (Windows): find scoop installation path by parsing scoop config file --- src/detection/packages/packages.h | 3 +- src/detection/packages/packages_windows.c | 46 +++++++++++++++++++---- src/modules/packages/packages.c | 20 ++++++++-- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h index 47c948f8c..e96b0762c 100644 --- a/src/detection/packages/packages.h +++ b/src/detection/packages/packages.h @@ -37,7 +37,8 @@ typedef struct FFPackagesResult uint32_t pkgsrc; uint32_t pkgtool; uint32_t rpm; - uint32_t scoop; + uint32_t scoopUser; + uint32_t scoopGlobal; uint32_t snap; uint32_t soar; uint32_t sorcery; diff --git a/src/detection/packages/packages_windows.c b/src/detection/packages/packages_windows.c index 1d13e0798..f4a497dd4 100644 --- a/src/detection/packages/packages_windows.c +++ b/src/detection/packages/packages_windows.c @@ -34,23 +34,53 @@ static uint32_t getNumElements(const char* searchPath /* including `\*` suffix * return counter; } +static inline void wrapYyjsonFree(yyjson_doc** doc) +{ + assert(doc); + if (*doc) + yyjson_doc_free(*doc); +} + static void detectScoop(FFPackagesResult* result) { FF_STRBUF_AUTO_DESTROY scoopPath = ffStrbufCreateA(MAX_PATH + 3); + ffStrbufAppendS(&scoopPath, instance.state.platform.homeDir.chars); + ffStrbufAppendS(&scoopPath, ".config/scoop/config.json"); - if(ffFindExecutableInPath("scoop.cmd", &scoopPath) == NULL) + yyjson_val* root = NULL; + + yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_file(scoopPath.chars, 0, NULL, NULL); + if (doc) { - // C:\Users\$USER\scoop\shims\scoop.cmd - ffStrbufSubstrBeforeLastC(&scoopPath, '\\'); - ffStrbufSubstrBeforeLastC(&scoopPath, '\\'); + root = yyjson_doc_get_root(doc); + if (!yyjson_is_obj(root)) root = NULL; + } + + { + ffStrbufClear(&scoopPath); + if (root) + ffStrbufSetS(&scoopPath, yyjson_get_str(yyjson_obj_get(root, "root_path"))); + if (scoopPath.length == 0) + { + ffStrbufSet(&scoopPath, &instance.state.platform.homeDir); + ffStrbufAppendS(&scoopPath, "/scoop"); + } ffStrbufAppendS(&scoopPath, "/apps/*"); + result->scoopUser = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, "scoop"); } - else + { - ffStrbufAppendS(&scoopPath, instance.state.platform.homeDir.chars); - ffStrbufAppendS(&scoopPath, "/scoop/apps/*"); + ffStrbufClear(&scoopPath); + if (root) + ffStrbufSetS(&scoopPath, yyjson_get_str(yyjson_obj_get(root, "global_path"))); + if (scoopPath.length == 0) + { + ffStrbufSetS(&scoopPath, getenv("ProgramData")); + ffStrbufAppendS(&scoopPath, "/scoop"); + } + ffStrbufAppendS(&scoopPath, "/apps/*"); + result->scoopGlobal = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, "scoop"); } - result->scoop = getNumElements(scoopPath.chars, FILE_ATTRIBUTE_DIRECTORY, "scoop"); } static void detectChoco(FF_MAYBE_UNUSED FFPackagesResult* result) diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index 4fb26080a..06c1817a3 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -23,6 +23,7 @@ void ffPrintPackages(FFPackagesOptions* options) uint32_t guixAll = counts.guixSystem + counts.guixUser + counts.guixHome; uint32_t hpkgAll = counts.hpkgSystem + counts.hpkgUser; uint32_t amAll = counts.amSystem + counts.amUser; + uint32_t scoopAll = counts.scoopUser + counts.scoopGlobal; if(options->moduleArgs.outputFormat.length == 0) { @@ -104,7 +105,15 @@ void ffPrintPackages(FFPackagesOptions* options) FF_PRINT_PACKAGE_NAME(brewCask, "brew-cask") } FF_PRINT_PACKAGE(macports) - FF_PRINT_PACKAGE(scoop) + if (options->combined) + { + FF_PRINT_PACKAGE_ALL(scoop); + } + else + { + FF_PRINT_PACKAGE_NAME(scoopUser, counts.scoopGlobal ? "scoop-user" : "scoop") + FF_PRINT_PACKAGE_NAME(scoopGlobal, "scoop-global") + } FF_PRINT_PACKAGE(choco) FF_PRINT_PACKAGE(pkgtool) FF_PRINT_PACKAGE(paludis) @@ -162,7 +171,8 @@ void ffPrintPackages(FFPackagesOptions* options) FF_FORMAT_ARG(counts.brew, "brew"), FF_FORMAT_ARG(counts.brewCask, "brew-cask"), FF_FORMAT_ARG(counts.macports, "macports"), - FF_FORMAT_ARG(counts.scoop, "scoop"), + FF_FORMAT_ARG(counts.scoopUser, "scoop-user"), + FF_FORMAT_ARG(counts.scoopGlobal, "scoop-global"), FF_FORMAT_ARG(counts.choco, "choco"), FF_FORMAT_ARG(counts.pkgtool, "pkgtool"), FF_FORMAT_ARG(counts.paludis, "paludis"), @@ -521,7 +531,8 @@ void ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions* options, yy FF_APPEND_PACKAGE_COUNT(pkgsrc) FF_APPEND_PACKAGE_COUNT(macports) FF_APPEND_PACKAGE_COUNT(rpm) - FF_APPEND_PACKAGE_COUNT(scoop) + FF_APPEND_PACKAGE_COUNT(scoopUser) + FF_APPEND_PACKAGE_COUNT(scoopGlobal) FF_APPEND_PACKAGE_COUNT(snap) FF_APPEND_PACKAGE_COUNT(soar) FF_APPEND_PACKAGE_COUNT(sorcery) @@ -558,7 +569,8 @@ static FFModuleBaseInfo ffModuleInfo = { {"Number of brew packages", "brew"}, {"Number of brew-cask packages", "brew-cask"}, {"Number of macports packages", "macports"}, - {"Number of scoop packages", "scoop"}, + {"Number of scoop-user packages", "scoop-user"}, + {"Number of scoop-global packages", "scoop-global"}, {"Number of choco packages", "choco"}, {"Number of pkgtool packages", "pkgtool"}, {"Number of paludis packages", "paludis"}, From 8be419cfd623a694612a8a2ee0d66ec0826de0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 30 Jul 2025 09:05:23 +0800 Subject: [PATCH 33/41] DiskIO: fix wrong `/s` assignment in custom format Fix #1871 --- src/modules/diskio/diskio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/diskio/diskio.c b/src/modules/diskio/diskio.c index 08e0ca84e..b73237783 100644 --- a/src/modules/diskio/diskio.c +++ b/src/modules/diskio/diskio.c @@ -68,11 +68,11 @@ void ffPrintDiskIO(FFDiskIOOptions* options) } else { - ffStrbufClear(&buffer2); ffParseSize(dev->bytesRead, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); + ffStrbufClear(&buffer2); ffParseSize(dev->bytesWritten, &buffer2); - if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); + if (!options->detectTotal) ffStrbufAppendS(&buffer2, "/s"); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ FF_FORMAT_ARG(buffer, "size-read"), From a0174e86660dbefed66fefbaae95f79a06a98b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 30 Jul 2025 14:30:36 +0800 Subject: [PATCH 34/41] Global: add new unit related options 1. `---space-before-unit` 2. `--duration-abbreviation` 3. various code refactoring and cleanups --- CMakeLists.txt | 5 +- doc/json_schema.json | 44 ++++++ src/common/duration.c | 87 ++++++++++++ src/common/duration.h | 3 + src/common/frequency.c | 17 +++ src/common/frequency.h | 3 + src/common/parsing.c | 106 --------------- src/common/parsing.h | 4 - src/common/percent.c | 4 +- src/common/size.c | 42 ++++++ src/common/size.h | 3 + src/common/temps.c | 9 +- src/data/help.json | 73 +++++++++- src/modules/battery/battery.c | 4 +- src/modules/btrfs/btrfs.c | 11 +- src/modules/cpu/cpu.c | 7 +- src/modules/cpucache/cpucache.c | 9 +- src/modules/disk/disk.c | 6 +- src/modules/diskio/diskio.c | 10 +- src/modules/display/display.c | 11 +- src/modules/gpu/gpu.c | 19 +-- src/modules/kernel/kernel.c | 3 +- src/modules/memory/memory.c | 6 +- src/modules/netio/netio.c | 10 +- src/modules/physicaldisk/physicaldisk.c | 4 +- src/modules/physicalmemory/physicalmemory.c | 4 +- src/modules/swap/swap.c | 6 +- src/modules/uptime/uptime.c | 3 +- src/modules/wifi/wifi.c | 5 +- src/modules/zpool/zpool.c | 5 +- src/options/display.c | 140 ++++++++++++++++++++ src/options/display.h | 14 ++ tests/duration.c | 53 +++++++- 33 files changed, 556 insertions(+), 174 deletions(-) create mode 100644 src/common/duration.c create mode 100644 src/common/duration.h create mode 100644 src/common/frequency.c create mode 100644 src/common/frequency.h create mode 100644 src/common/size.c create mode 100644 src/common/size.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e008886a7..f66e830c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,10 +351,11 @@ file(GENERATE OUTPUT logo_builtin.h CONTENT "${LOGO_BUILTIN_H}") ####################### set(LIBFASTFETCH_SRC - src/common/percent.c src/common/commandoption.c + src/common/duration.c src/common/font.c src/common/format.c + src/common/frequency.c src/common/init.c src/common/jsonconfig.c src/common/library.c @@ -363,9 +364,11 @@ set(LIBFASTFETCH_SRC src/common/networking/networking_common.c src/common/option.c src/common/parsing.c + src/common/percent.c src/common/printing.c src/common/properties.c src/common/settings.c + src/common/size.c src/common/temps.c src/detection/bluetoothradio/bluetoothradio.c src/detection/bootmgr/bootmgr.c diff --git a/doc/json_schema.json b/doc/json_schema.json index 80924361c..ffc056d75 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -232,6 +232,24 @@ } } }, + "spaceBeforeUnit": { + "type": "string", + "description": "Whether to put a space before the unit", + "oneOf": [ + { + "const": "default", + "description": "Use the default behavior of the module" + }, + { + "const": "always", + "description": "Always add a space before the unit" + }, + { + "const": "never", + "description": "Never add a space before the unit" + } + ] + }, "batteryFormat": { @@ -923,6 +941,9 @@ "minimum": 0, "maximum": 9, "default": 2 + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" } } }, @@ -965,6 +986,9 @@ "default": "light_red" } } + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" } } }, @@ -1037,6 +1061,9 @@ "default": "light_red" } } + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" } } }, @@ -1051,6 +1078,23 @@ "minimum": -1, "maximum": 9, "default": 2 + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" + } + } + }, + "duration": { + "type": "object", + "description": "Set how duration values should be displayed", + "properties": { + "abbreviation": { + "type": "boolean", + "description": "Set whether to abbreviate duration values\nIf true, the output will be in the form of \"1h 2m\" instead of \"1 hour, 2 mins\"", + "default": false + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" } } }, diff --git a/src/common/duration.c b/src/common/duration.c new file mode 100644 index 000000000..16e5570e9 --- /dev/null +++ b/src/common/duration.c @@ -0,0 +1,87 @@ +#include "duration.h" + +void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result) +{ + const FFOptionsDisplay* options = &instance.config.display; + + const char* space = instance.config.display.durationSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER ? " " : ""; + + if(totalSeconds < 60) + { + ffStrbufAppendF(result, options->durationAbbreviation ? "%u%ssec" : "%u%ssecond", (unsigned) totalSeconds, space); + if (totalSeconds != 1) + ffStrbufAppendC(result, 's'); + return; + } + + uint32_t seconds = (uint32_t) (totalSeconds % 60); + totalSeconds /= 60; + if (seconds >= 30) + totalSeconds++; + + uint32_t minutes = (uint32_t) (totalSeconds % 60); + totalSeconds /= 60; + uint32_t hours = (uint32_t) (totalSeconds % 24); + totalSeconds /= 24; + uint32_t days = (uint32_t) totalSeconds; + + if(days > 0) + { + if(options->durationAbbreviation) + { + ffStrbufAppendF(result, "%u%sd", days, space); + + if(hours > 0 || minutes > 0) + ffStrbufAppendC(result, ' '); + } + else + { + ffStrbufAppendF(result, "%u%sday", days, space); + + if(days > 1) + ffStrbufAppendC(result, 's'); + + if(days >= 100) + ffStrbufAppendS(result, "(!)"); + + if(hours > 0 || minutes > 0) + ffStrbufAppendS(result, ", "); + } + } + + if(hours > 0) + { + if(options->durationAbbreviation) + { + ffStrbufAppendF(result, "%u%sh", hours, space); + + if (minutes > 0) + ffStrbufAppendC(result, ' '); + } + else + { + ffStrbufAppendF(result, "%u%shour", hours, space); + + if(hours > 1) + ffStrbufAppendC(result, 's'); + + if(minutes > 0) + ffStrbufAppendS(result, ", "); + } + } + + if(minutes > 0) + { + if(options->durationAbbreviation) + { + ffStrbufAppendF(result, "%u%sm", minutes, space); + } + else + { + ffStrbufAppendF(result, "%u%smin", minutes, space); + + if(minutes > 1) + ffStrbufAppendC(result, 's'); + } + } +} diff --git a/src/common/duration.h b/src/common/duration.h new file mode 100644 index 000000000..234a2c976 --- /dev/null +++ b/src/common/duration.h @@ -0,0 +1,3 @@ +#include "fastfetch.h" + +void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result); diff --git a/src/common/frequency.c b/src/common/frequency.c new file mode 100644 index 000000000..248bb0638 --- /dev/null +++ b/src/common/frequency.c @@ -0,0 +1,17 @@ +#include "frequency.h" + +bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result) +{ + if (mhz == 0) + return false; + + const FFOptionsDisplay* options = &instance.config.display; + const char* space = options->freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "; + int8_t ndigits = options->freqNdigits; + + if (ndigits >= 0) + ffStrbufAppendF(result, "%.*f%sGHz", ndigits, mhz / 1000., space); + else + ffStrbufAppendF(result, "%u%sMHz", (unsigned) mhz, space); + return true; +} diff --git a/src/common/frequency.h b/src/common/frequency.h new file mode 100644 index 000000000..53dacf803 --- /dev/null +++ b/src/common/frequency.h @@ -0,0 +1,3 @@ +#include "fastfetch.h" + +bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result); diff --git a/src/common/parsing.c b/src/common/parsing.c index 7aeea191e..00de9a8d5 100644 --- a/src/common/parsing.c +++ b/src/common/parsing.c @@ -2,7 +2,6 @@ #include "common/parsing.h" #include -#include #ifdef _WIN32 #pragma GCC diagnostic push @@ -60,56 +59,6 @@ void ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty) ffStrbufAppendF(pretty, ".%u", version->patch); } -static void parseSize(FFstrbuf* result, uint64_t bytes, uint32_t base, const char** prefixes) -{ - double size = (double) bytes; - uint8_t counter = 0; - - while(size >= base && counter < instance.config.display.sizeMaxPrefix && prefixes[counter + 1]) - { - size /= base; - counter++; - } - - if(counter == 0) - ffStrbufAppendF(result, "%" PRIu64 " %s", bytes, prefixes[0]); - else - ffStrbufAppendF(result, "%.*f %s", instance.config.display.sizeNdigits, size, prefixes[counter]); -} - -void ffParseSize(uint64_t bytes, FFstrbuf* result) -{ - switch (instance.config.display.sizeBinaryPrefix) - { - case FF_SIZE_BINARY_PREFIX_TYPE_IEC: - parseSize(result, bytes, 1024, (const char*[]) {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", NULL}); - break; - case FF_SIZE_BINARY_PREFIX_TYPE_SI: - parseSize(result, bytes, 1000, (const char*[]) {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL}); - break; - case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC: - parseSize(result, bytes, 1024, (const char*[]) {"B", "KB", "MB", "GB", "TB", NULL}); - break; - default: - parseSize(result, bytes, 1024, (const char*[]) {"B", NULL}); - break; - } -} - -bool ffParseFrequency(uint32_t mhz, FFstrbuf* result) -{ - if (mhz == 0) - return false; - - int8_t ndigits = instance.config.display.freqNdigits; - - if (ndigits >= 0) - ffStrbufAppendF(result, "%.*f GHz", ndigits, mhz / 1000.); - else - ffStrbufAppendF(result, "%u MHz", (unsigned) mhz); - return true; -} - void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, const FFstrbuf* gtk4) { if(gtk2->length > 0 && gtk3->length > 0 && gtk4->length > 0) @@ -190,61 +139,6 @@ void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, co } } -void ffParseDuration(uint64_t totalSeconds, FFstrbuf* result) -{ - if(totalSeconds < 60) - { - ffStrbufAppendF(result, "%u second", (unsigned) totalSeconds); - if (totalSeconds != 1) - ffStrbufAppendC(result, 's'); - return; - } - - uint32_t seconds = (uint32_t) (totalSeconds % 60); - totalSeconds /= 60; - if (seconds >= 30) - totalSeconds++; - - uint32_t minutes = (uint32_t) (totalSeconds % 60); - totalSeconds /= 60; - uint32_t hours = (uint32_t) (totalSeconds % 24); - totalSeconds /= 24; - uint32_t days = (uint32_t) totalSeconds; - - if(days > 0) - { - ffStrbufAppendF(result, "%u day", days); - - if(days > 1) - ffStrbufAppendC(result, 's'); - - if(days >= 100) - ffStrbufAppendS(result, "(!)"); - - if(hours > 0 || minutes > 0) - ffStrbufAppendS(result, ", "); - } - - if(hours > 0) - { - ffStrbufAppendF(result, "%u hour", hours); - - if(hours > 1) - ffStrbufAppendC(result, 's'); - - if(minutes > 0) - ffStrbufAppendS(result, ", "); - } - - if(minutes > 0) - { - ffStrbufAppendF(result, "%u min", minutes); - - if(minutes > 1) - ffStrbufAppendC(result, 's'); - } -} - #ifdef _WIN32 #pragma GCC diagnostic pop #endif diff --git a/src/common/parsing.h b/src/common/parsing.h index af72f441d..f18987906 100644 --- a/src/common/parsing.h +++ b/src/common/parsing.h @@ -24,7 +24,3 @@ void ffParseGTK(FFstrbuf* buffer, const FFstrbuf* gtk2, const FFstrbuf* gtk3, co void ffVersionToPretty(const FFVersion* version, FFstrbuf* pretty); int8_t ffVersionCompare(const FFVersion* version1, const FFVersion* version2); - -void ffParseSize(uint64_t bytes, FFstrbuf* result); -bool ffParseFrequency(uint32_t mhz, FFstrbuf* result); -void ffParseDuration(uint64_t totalSeconds, FFstrbuf* result); diff --git a/src/common/percent.c b/src/common/percent.c index 8a1dffc14..8e4a0c24d 100644 --- a/src/common/percent.c +++ b/src/common/percent.c @@ -175,7 +175,6 @@ void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConf ffStrbufAppendF(buffer, "\e[%sm", colorYellow); else ffStrbufAppendF(buffer, "\e[%sm", colorGreen); - } else { @@ -187,7 +186,8 @@ void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConf ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } } - ffStrbufAppendF(buffer, "%.*f%%", options->percentNdigits, percent); + ffStrbufAppendF(buffer, "%.*f%s%%", options->percentNdigits, percent, + options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); if (colored && !options->pipe) { diff --git a/src/common/size.c b/src/common/size.c new file mode 100644 index 000000000..f32b02181 --- /dev/null +++ b/src/common/size.c @@ -0,0 +1,42 @@ +#include "size.h" + +#include + +static void appendNum(FFstrbuf* result, uint64_t bytes, uint32_t base, const char** prefixes) +{ + const FFOptionsDisplay* options = &instance.config.display; + double size = (double) bytes; + uint8_t counter = 0; + + while(size >= base && counter < options->sizeMaxPrefix && prefixes[counter + 1]) + { + size /= base; + counter++; + } + + const char* space = options->sizeSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "; + if(counter == 0) + ffStrbufAppendF(result, "%" PRIu64 "%s%s", bytes, space, prefixes[0]); + else + ffStrbufAppendF(result, "%.*f%s%s", options->sizeNdigits, size, space, prefixes[counter]); +} + +void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result) +{ + const FFOptionsDisplay* options = &instance.config.display; + switch (options->sizeBinaryPrefix) + { + case FF_SIZE_BINARY_PREFIX_TYPE_IEC: + appendNum(result, bytes, 1024, (const char*[]) {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", NULL}); + break; + case FF_SIZE_BINARY_PREFIX_TYPE_SI: + appendNum(result, bytes, 1000, (const char*[]) {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", NULL}); + break; + case FF_SIZE_BINARY_PREFIX_TYPE_JEDEC: + appendNum(result, bytes, 1024, (const char*[]) {"B", "KB", "MB", "GB", "TB", NULL}); + break; + default: + appendNum(result, bytes, 1024, (const char*[]) {"B", NULL}); + break; + } +} diff --git a/src/common/size.h b/src/common/size.h new file mode 100644 index 000000000..f234d5ae0 --- /dev/null +++ b/src/common/size.h @@ -0,0 +1,3 @@ +#include "fastfetch.h" + +void ffSizeAppendNum(uint64_t bytes, FFstrbuf* result); diff --git a/src/common/temps.c b/src/common/temps.c index 10e11de61..3e1e59c54 100644 --- a/src/common/temps.c +++ b/src/common/temps.c @@ -41,13 +41,16 @@ void ffTempsAppendNum(double celsius, FFstrbuf* buffer, FFColorRangeConfig confi { case FF_TEMPERATURE_UNIT_DEFAULT: case FF_TEMPERATURE_UNIT_CELSIUS: - ffStrbufAppendF(buffer, "%.*f°C", options->tempNdigits, celsius); + ffStrbufAppendF(buffer, "%.*f%s°C", options->tempNdigits, celsius, + options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); break; case FF_TEMPERATURE_UNIT_FAHRENHEIT: - ffStrbufAppendF(buffer, "%.*f°F", options->tempNdigits, celsius * 1.8 + 32); + ffStrbufAppendF(buffer, "%.*f%s°F", options->tempNdigits, celsius * 1.8 + 32, + options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); break; case FF_TEMPERATURE_UNIT_KELVIN: - ffStrbufAppendF(buffer, "%.*f K", options->tempNdigits, celsius + 273.15); + ffStrbufAppendF(buffer, "%.*f%sK", options->tempNdigits, celsius + 273.15, + options->tempSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "); break; } diff --git a/src/data/help.json b/src/data/help.json index 788428a23..552affcca 100644 --- a/src/data/help.json +++ b/src/data/help.json @@ -454,7 +454,8 @@ "remark": "Auto-detected based on isatty(1) by default", "arg": { "type": "bool", - "optional": true + "optional": true, + "default": false } }, { @@ -500,6 +501,28 @@ "type": "color" } }, + { + "long": "duration-abbreviation", + "desc": "Specify whether to abbreviate duration values", + "remark": "If true, the output will be in the form of \"1h 2m\" instead of \"1 hour, 2 mins\"", + "arg": { + "type": "bool", + "optional": true, + "default": false + } + }, + { + "long": "duration-space-before-unit", + "desc": "Specify whether to put a space before the unit in duration values", + "arg": { + "type": "enum", + "enum": { + "default": "Use the default behavior of the module", + "always": "Always put a space before the unit", + "never": "Never put a space before the unit" + } + } + }, { "long": "key-width", "desc": "Align the width of keys to characters", @@ -623,6 +646,18 @@ "default": "light_red" } }, + { + "long": "percent-space-before-unit", + "desc": "Specify whether to put a space before the percentage symbol", + "arg": { + "type": "enum", + "enum": { + "default": "Use the default behavior of the module", + "always": "Always put a space before the unit", + "never": "Never put a space before the unit" + } + } + }, { "long": "bar-char-elapsed", "desc": "Set the character to use in the elapsed part of percentage bars", @@ -711,6 +746,18 @@ "default": "YB" } }, + { + "long": "size-space-before-unit", + "desc": "Specify whether to put a space before the unit", + "arg": { + "type": "enum", + "enum": { + "default": "Use the default behavior of the module", + "always": "Always put a space before the unit", + "never": "Never put a space before the unit" + } + } + }, { "long": "freq-ndigits", "desc": "Set the number of digits to keep after the decimal point when printing CPU/GPU frequency in GHz", @@ -719,6 +766,18 @@ "default": 2 } }, + { + "long": "freq-space-before-unit", + "desc": "Specify whether to put a space before the unit", + "arg": { + "type": "enum", + "enum": { + "default": "Use the default behavior of the module", + "always": "Always put a space before the unit", + "never": "Never put a space before the unit" + } + } + }, { "long": "fraction-ndigits", "desc": "Set the number of digits to keep after the decimal point when printing ordinary fraction numbers", @@ -776,6 +835,18 @@ "type": "color", "default": "light_red" } + }, + { + "long": "temp-space-before-unit", + "desc": "Specify whether to put a space before the unit", + "arg": { + "type": "enum", + "enum": { + "default": "Use the default behavior of the module", + "always": "Always put a space before the unit", + "never": "Never put a space before the unit" + } + } } ], "Module specific": [ diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index ba2327e34..b45995b35 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -1,7 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" -#include "common/parsing.h" +#include "common/duration.h" #include "common/temps.h" #include "detection/battery/battery.h" #include "modules/battery/battery.h" @@ -59,7 +59,7 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin if(str.length > 0) ffStrbufAppendS(&str, " ("); - ffParseDuration((uint32_t) result->timeRemaining, &str); + ffDurationAppendNum((uint32_t) result->timeRemaining, &str); ffStrbufAppendS(&str, " remaining)"); } } diff --git a/src/modules/btrfs/btrfs.c b/src/modules/btrfs/btrfs.c index 02ebf6032..31b58e1f3 100644 --- a/src/modules/btrfs/btrfs.c +++ b/src/modules/btrfs/btrfs.c @@ -1,6 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" +#include "common/size.h" #include "detection/btrfs/btrfs.h" #include "modules/btrfs/btrfs.h" #include "util/stringUtils.h" @@ -34,11 +35,11 @@ static void printBtrfs(FFBtrfsOptions* options, FFBtrfsResult* result, uint8_t i } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); - ffParseSize(used, &usedPretty); + ffSizeAppendNum(used, &usedPretty); FF_STRBUF_AUTO_DESTROY allocatedPretty = ffStrbufCreate(); - ffParseSize(allocated, &allocatedPretty); + ffSizeAppendNum(allocated, &allocatedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); - ffParseSize(total, &totalPretty); + ffSizeAppendNum(total, &totalPretty); double usedPercentage = total > 0 ? (double) used / (double) total * 100.0 : 0; double allocatedPercentage = total > 0 ? (double) allocated / (double) total * 100.0 : 0; @@ -74,9 +75,9 @@ static void printBtrfs(FFBtrfsOptions* options, FFBtrfsResult* result, uint8_t i ffPercentAppendBar(&allocatedPercentageBar, allocatedPercentage, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY nodeSizePretty = ffStrbufCreate(); - ffParseSize(result->nodeSize, &nodeSizePretty); + ffSizeAppendNum(result->nodeSize, &nodeSizePretty); FF_STRBUF_AUTO_DESTROY sectorSizePretty = ffStrbufCreate(); - ffParseSize(result->sectorSize, §orSizePretty); + ffSizeAppendNum(result->sectorSize, §orSizePretty); FF_PRINT_FORMAT_CHECKED(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_FORMAT_ARG(result->name, "name"), diff --git a/src/modules/cpu/cpu.c b/src/modules/cpu/cpu.c index 178574aa0..2511a6b04 100644 --- a/src/modules/cpu/cpu.c +++ b/src/modules/cpu/cpu.c @@ -2,6 +2,7 @@ #include "common/jsonconfig.h" #include "common/parsing.h" #include "common/temps.h" +#include "common/frequency.h" #include "detection/cpu/cpu.h" #include "modules/cpu/cpu.h" #include "util/stringUtils.h" @@ -77,7 +78,7 @@ void ffPrintCPU(FFCPUOptions* options) if(freq > 0) { ffStrbufAppendS(&str, " @ "); - ffParseFrequency(freq, &str); + ffFreqAppendNum(freq, &str); } if(cpu.temperature == cpu.temperature) //FF_CPU_TEMP_UNSET @@ -91,9 +92,9 @@ void ffPrintCPU(FFCPUOptions* options) else { FF_STRBUF_AUTO_DESTROY freqBase = ffStrbufCreate(); - ffParseFrequency(cpu.frequencyBase, &freqBase); + ffFreqAppendNum(cpu.frequencyBase, &freqBase); FF_STRBUF_AUTO_DESTROY freqMax = ffStrbufCreate(); - ffParseFrequency(cpu.frequencyMax, &freqMax); + ffFreqAppendNum(cpu.frequencyMax, &freqMax); FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); ffTempsAppendNum(cpu.temperature, &tempStr, options->tempConfig, &options->moduleArgs); diff --git a/src/modules/cpucache/cpucache.c b/src/modules/cpucache/cpucache.c index 2be8f4551..64eaa0612 100644 --- a/src/modules/cpucache/cpucache.c +++ b/src/modules/cpucache/cpucache.c @@ -1,5 +1,6 @@ #include "common/printing.h" #include "common/jsonconfig.h" +#include "common/size.h" #include "detection/cpucache/cpucache.h" #include "modules/cpucache/cpucache.h" #include "util/stringUtils.h" @@ -45,7 +46,7 @@ static void printCPUCacheNormal(const FFCPUCacheResult* result, FFCPUCacheOption ffStrbufAppendS(&buffer, ", "); if (src->num > 1) ffStrbufAppendF(&buffer, "%ux", src->num); - ffParseSize(src->size, &buffer); + ffSizeAppendNum(src->size, &buffer); ffStrbufAppendF(&buffer, " (%c)", typeStr); sum += src->size * src->num; @@ -59,7 +60,7 @@ static void printCPUCacheNormal(const FFCPUCacheResult* result, FFCPUCacheOption else { FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate(); - ffParseSize(sum, &buffer2); + ffSizeAppendNum(sum, &buffer2); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_FORMAT_ARG(buffer, "result"), FF_FORMAT_ARG(buffer2, "sum"), @@ -79,7 +80,7 @@ static void printCPUCacheCompact(const FFCPUCacheResult* result, FFCPUCacheOptio uint32_t value = 0; FF_LIST_FOR_EACH(FFCPUCache, src, result->caches[i]) value += src->size * src->num; - ffParseSize(value, &buffer); + ffSizeAppendNum(value, &buffer); ffStrbufAppendF(&buffer, " (L%u)", i + 1); sum += value; } @@ -92,7 +93,7 @@ static void printCPUCacheCompact(const FFCPUCacheResult* result, FFCPUCacheOptio else { FF_STRBUF_AUTO_DESTROY buffer2 = ffStrbufCreate(); - ffParseSize(sum, &buffer2); + ffSizeAppendNum(sum, &buffer2); FF_PRINT_FORMAT_CHECKED(FF_CPUCACHE_DISPLAY_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_FORMAT_ARG(buffer, "result"), FF_FORMAT_ARG(buffer2, "sum"), diff --git a/src/modules/disk/disk.c b/src/modules/disk/disk.c index b84012443..1596b3861 100644 --- a/src/modules/disk/disk.c +++ b/src/modules/disk/disk.c @@ -1,7 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" #include "common/percent.h" +#include "common/size.h" #include "common/time.h" #include "detection/disk/disk.h" #include "modules/disk/disk.h" @@ -44,10 +44,10 @@ static void printDisk(FFDiskOptions* options, const FFDisk* disk, uint32_t index } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); - ffParseSize(disk->bytesUsed, &usedPretty); + ffSizeAppendNum(disk->bytesUsed, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); - ffParseSize(disk->bytesTotal, &totalPretty); + ffSizeAppendNum(disk->bytesTotal, &totalPretty); double bytesPercentage = disk->bytesTotal > 0 ? (double) disk->bytesUsed / (double) disk->bytesTotal * 100.0 : 0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; diff --git a/src/modules/diskio/diskio.c b/src/modules/diskio/diskio.c index b73237783..19af8eb3c 100644 --- a/src/modules/diskio/diskio.c +++ b/src/modules/diskio/diskio.c @@ -1,6 +1,6 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" +#include "common/size.h" #include "detection/diskio/diskio.h" #include "modules/diskio/diskio.h" #include "util/stringUtils.h" @@ -57,21 +57,21 @@ void ffPrintDiskIO(FFDiskIOOptions* options) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); - ffParseSize(dev->bytesRead, &buffer); + ffSizeAppendNum(dev->bytesRead, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (R) - "); - ffParseSize(dev->bytesWritten, &buffer); + ffSizeAppendNum(dev->bytesWritten, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (W)"); ffStrbufPutTo(&buffer, stdout); } else { - ffParseSize(dev->bytesRead, &buffer); + ffSizeAppendNum(dev->bytesRead, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufClear(&buffer2); - ffParseSize(dev->bytesWritten, &buffer2); + ffSizeAppendNum(dev->bytesWritten, &buffer2); if (!options->detectTotal) ffStrbufAppendS(&buffer2, "/s"); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ diff --git a/src/modules/display/display.c b/src/modules/display/display.c index a55c15d83..437bc79f6 100644 --- a/src/modules/display/display.c +++ b/src/modules/display/display.c @@ -1,5 +1,6 @@ #include "common/printing.h" #include "common/jsonconfig.h" +#include "common/size.h" #include "detection/displayserver/displayserver.h" #include "modules/display/display.h" #include "util/stringUtils.h" @@ -51,10 +52,11 @@ void ffPrintDisplay(FFDisplayOptions* options) { if (result->refreshRate > 0) { + const char* space = instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""; if (options->preciseRefreshRate) - ffStrbufAppendF(&buffer, " @ %gHz", result->refreshRate); + ffStrbufAppendF(&buffer, " @ %g%sHz", result->refreshRate, space); else - ffStrbufAppendF(&buffer, " @ %iHz", (uint32_t) (result->refreshRate + 0.5)); + ffStrbufAppendF(&buffer, " @ %i%sHz", (uint32_t) (result->refreshRate + 0.5), space); } ffStrbufAppendS(&buffer, ", "); } @@ -108,10 +110,11 @@ void ffPrintDisplay(FFDisplayOptions* options) if(result->refreshRate > 0) { + const char* space = instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "; if(options->preciseRefreshRate) - ffStrbufAppendF(&buffer, " @ %g Hz", ((int) (result->refreshRate * 1000 + 0.5)) / 1000.0); + ffStrbufAppendF(&buffer, " @ %g%sHz", ((int) (result->refreshRate * 1000 + 0.5)) / 1000.0, space); else - ffStrbufAppendF(&buffer, " @ %i Hz", (uint32_t) (result->refreshRate + 0.5)); + ffStrbufAppendF(&buffer, " @ %i%sHz", (uint32_t) (result->refreshRate + 0.5), space); } if( diff --git a/src/modules/gpu/gpu.c b/src/modules/gpu/gpu.c index 7d917c83d..f2f5806c7 100644 --- a/src/modules/gpu/gpu.c +++ b/src/modules/gpu/gpu.c @@ -1,8 +1,9 @@ #include "common/percent.h" -#include "common/parsing.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/temps.h" +#include "common/size.h" +#include "common/frequency.h" #include "detection/host/host.h" #include "detection/gpu/gpu.h" #include "modules/gpu/gpu.h" @@ -42,7 +43,7 @@ static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResu if(gpu->frequency > 0) { ffStrbufAppendS(&output, " @ "); - ffParseFrequency(gpu->frequency, &output); + ffFreqAppendNum(gpu->frequency, &output); } if(gpu->temperature == gpu->temperature) //FF_GPU_TEMP_UNSET @@ -59,10 +60,10 @@ static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResu { if(gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) { - ffParseSize(gpu->dedicated.used, &output); + ffSizeAppendNum(gpu->dedicated.used, &output); ffStrbufAppendS(&output, " / "); } - ffParseSize(gpu->dedicated.total, &output); + ffSizeAppendNum(gpu->dedicated.total, &output); } if(gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) { @@ -94,8 +95,8 @@ static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResu FF_STRBUF_AUTO_DESTROY dUsed = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY dPercentNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY dPercentBar = ffStrbufCreate(); - if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) ffParseSize(gpu->dedicated.total, &dTotal); - if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) ffParseSize(gpu->dedicated.used, &dUsed); + if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->dedicated.total, &dTotal); + if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->dedicated.used, &dUsed); if (gpu->dedicated.total != FF_GPU_VMEM_SIZE_UNSET && gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) { double percent = (double) gpu->dedicated.used / (double) gpu->dedicated.total * 100.0; @@ -109,8 +110,8 @@ static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResu FF_STRBUF_AUTO_DESTROY sUsed = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sPercentNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY sPercentBar = ffStrbufCreate(); - if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) ffParseSize(gpu->shared.total, &sTotal); - if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) ffParseSize(gpu->shared.used, &sUsed); + if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->shared.total, &sTotal); + if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) ffSizeAppendNum(gpu->shared.used, &sUsed); if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET && gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) { double percent = (double) gpu->shared.used / (double) gpu->shared.total * 100.0; @@ -121,7 +122,7 @@ static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResu } FF_STRBUF_AUTO_DESTROY frequency = ffStrbufCreate(); - ffParseFrequency(gpu->frequency, &frequency); + ffFreqAppendNum(gpu->frequency, &frequency); FF_STRBUF_AUTO_DESTROY coreUsageNum = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY coreUsageBar = ffStrbufCreate(); diff --git a/src/modules/kernel/kernel.c b/src/modules/kernel/kernel.c index 44c85668b..d747e6752 100644 --- a/src/modules/kernel/kernel.c +++ b/src/modules/kernel/kernel.c @@ -1,5 +1,6 @@ #include "common/printing.h" #include "common/jsonconfig.h" +#include "common/size.h" #include "modules/kernel/kernel.h" #include "util/stringUtils.h" @@ -19,7 +20,7 @@ void ffPrintKernel(FFKernelOptions* options) else { FF_STRBUF_AUTO_DESTROY str = ffStrbufCreate(); - ffParseSize(info->pageSize, &str); + ffSizeAppendNum(info->pageSize, &str); FF_PRINT_FORMAT_CHECKED(FF_KERNEL_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ FF_FORMAT_ARG(info->name, "sysname"), FF_FORMAT_ARG(info->release, "release"), diff --git a/src/modules/memory/memory.c b/src/modules/memory/memory.c index 0af03736d..deac7ef91 100644 --- a/src/modules/memory/memory.c +++ b/src/modules/memory/memory.c @@ -1,7 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" #include "common/percent.h" +#include "common/size.h" #include "detection/memory/memory.h" #include "modules/memory/memory.h" #include "util/stringUtils.h" @@ -18,10 +18,10 @@ void ffPrintMemory(FFMemoryOptions* options) } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); - ffParseSize(storage.bytesUsed, &usedPretty); + ffSizeAppendNum(storage.bytesUsed, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); - ffParseSize(storage.bytesTotal, &totalPretty); + ffSizeAppendNum(storage.bytesTotal, &totalPretty); double percentage = storage.bytesTotal == 0 ? 0 diff --git a/src/modules/netio/netio.c b/src/modules/netio/netio.c index d49ca61f9..5b0fb4bbb 100644 --- a/src/modules/netio/netio.c +++ b/src/modules/netio/netio.c @@ -1,6 +1,6 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" +#include "common/size.h" #include "detection/netio/netio.h" #include "modules/netio/netio.h" #include "util/stringUtils.h" @@ -59,11 +59,11 @@ void ffPrintNetIO(FFNetIOOptions* options) { ffPrintLogoAndKey(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY); - ffParseSize(inf->rxBytes, &buffer); + ffSizeAppendNum(inf->rxBytes, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (IN) - "); - ffParseSize(inf->txBytes, &buffer); + ffSizeAppendNum(inf->txBytes, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); ffStrbufAppendS(&buffer, " (OUT)"); @@ -74,9 +74,9 @@ void ffPrintNetIO(FFNetIOOptions* options) else { ffStrbufClear(&buffer2); - ffParseSize(inf->rxBytes, &buffer); + ffSizeAppendNum(inf->rxBytes, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); - ffParseSize(inf->txBytes, &buffer2); + ffSizeAppendNum(inf->txBytes, &buffer2); if (!options->detectTotal) ffStrbufAppendS(&buffer2, "/s"); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]){ diff --git a/src/modules/physicaldisk/physicaldisk.c b/src/modules/physicaldisk/physicaldisk.c index de6751791..f68770697 100644 --- a/src/modules/physicaldisk/physicaldisk.c +++ b/src/modules/physicaldisk/physicaldisk.c @@ -1,7 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" #include "common/temps.h" +#include "common/size.h" #include "detection/physicaldisk/physicaldisk.h" #include "modules/physicaldisk/physicaldisk.h" #include "util/stringUtils.h" @@ -52,7 +52,7 @@ void ffPrintPhysicalDisk(FFPhysicalDiskOptions* options) { formatKey(options, dev, result.length == 1 ? 0 : index + 1, &key); ffStrbufClear(&buffer); - ffParseSize(dev->size, &buffer); + ffSizeAppendNum(dev->size, &buffer); const char* physicalType = dev->type & FF_PHYSICALDISK_TYPE_HDD ? "HDD" diff --git a/src/modules/physicalmemory/physicalmemory.c b/src/modules/physicalmemory/physicalmemory.c index 5ea3573a9..4bfe28ef4 100644 --- a/src/modules/physicalmemory/physicalmemory.c +++ b/src/modules/physicalmemory/physicalmemory.c @@ -1,7 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" #include "common/percent.h" +#include "common/size.h" #include "detection/physicalmemory/physicalmemory.h" #include "modules/physicalmemory/physicalmemory.h" #include "util/stringUtils.h" @@ -32,7 +32,7 @@ void ffPrintPhysicalMemory(FFPhysicalMemoryOptions* options) { ++i; ffStrbufClear(&prettySize); - ffParseSize(device->size, &prettySize); + ffSizeAppendNum(device->size, &prettySize); if (options->moduleArgs.outputFormat.length == 0) { diff --git a/src/modules/swap/swap.c b/src/modules/swap/swap.c index c4a28f67b..fb3072f44 100644 --- a/src/modules/swap/swap.c +++ b/src/modules/swap/swap.c @@ -1,7 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/parsing.h" #include "common/percent.h" +#include "common/size.h" #include "detection/swap/swap.h" #include "modules/swap/swap.h" #include "util/stringUtils.h" @@ -28,10 +28,10 @@ void printSwap(FFSwapOptions* options, uint8_t index, FFSwapResult* storage) } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); - ffParseSize(storage->bytesUsed, &usedPretty); + ffSizeAppendNum(storage->bytesUsed, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); - ffParseSize(storage->bytesTotal, &totalPretty); + ffSizeAppendNum(storage->bytesTotal, &totalPretty); double percentage = storage->bytesTotal == 0 ? 0 diff --git a/src/modules/uptime/uptime.c b/src/modules/uptime/uptime.c index 445de7e1f..c0118686a 100644 --- a/src/modules/uptime/uptime.c +++ b/src/modules/uptime/uptime.c @@ -1,3 +1,4 @@ +#include "common/duration.h" #include "common/printing.h" #include "common/jsonconfig.h" #include "common/time.h" @@ -23,7 +24,7 @@ void ffPrintUptime(FFUptimeOptions* options) { ffPrintLogoAndKey(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - ffParseDuration((uptime + 500) / 1000, &buffer); + ffDurationAppendNum((uptime + 500) / 1000, &buffer); ffStrbufPutTo(&buffer, stdout); } diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c index cae5150dd..c7038a0fc 100644 --- a/src/modules/wifi/wifi.c +++ b/src/modules/wifi/wifi.c @@ -74,7 +74,10 @@ void ffPrintWifi(FFWifiOptions* options) ffStrbufAppend(&buffer, &item->conn.protocol); } if (bandStr[0]) - ffStrbufAppendF(&buffer, " - %s GHz", bandStr); + { + ffStrbufAppendF(&buffer, " - %s%sGHz", bandStr, + instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_NEVER ? "" : " "); + } if(item->conn.security.length) { ffStrbufAppendS(&buffer, " - "); diff --git a/src/modules/zpool/zpool.c b/src/modules/zpool/zpool.c index a00274e70..151836f0b 100644 --- a/src/modules/zpool/zpool.c +++ b/src/modules/zpool/zpool.c @@ -1,6 +1,7 @@ #include "common/printing.h" #include "common/jsonconfig.h" #include "common/percent.h" +#include "common/size.h" #include "detection/zpool/zpool.h" #include "modules/zpool/zpool.h" #include "util/stringUtils.h" @@ -26,10 +27,10 @@ static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t i } FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); - ffParseSize(result->used, &usedPretty); + ffSizeAppendNum(result->used, &usedPretty); FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); - ffParseSize(result->total, &totalPretty); + ffSizeAppendNum(result->total, &totalPretty); double bytesPercentage = result->total > 0 ? (double) result->used / (double) result->total * 100.0 : 0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; diff --git a/src/options/display.c b/src/options/display.c index cc4b703e6..5eb1e33e5 100644 --- a/src/options/display.c +++ b/src/options/display.c @@ -78,6 +78,28 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va options->brightColor = yyjson_get_bool(val); else if (ffStrEqualsIgnCase(key, "binaryPrefix")) return "`display.binaryPrefix` has been renamed to `display.size.binaryPrefix`. Sorry for another break change."; + else if (ffStrEqualsIgnCase(key, "duration")) + { + if (!yyjson_is_obj(val)) + return "display.duration must be an object"; + + yyjson_val* abbreviation = yyjson_obj_get(val, "abbreviation"); + if (abbreviation) options->durationAbbreviation = yyjson_get_bool(abbreviation); + + yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); + if (spaceBeforeUnit) + { + int value; + const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + if (error) return error; + options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + } + } else if (ffStrEqualsIgnCase(key, "size")) { if (!yyjson_is_obj(val)) @@ -119,6 +141,20 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) options->sizeNdigits = (uint8_t) yyjson_get_uint(ndigits); + + yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); + if (spaceBeforeUnit) + { + int value; + const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + if (error) return error; + options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + } } else if (ffStrEqualsIgnCase(key, "temp")) { @@ -162,6 +198,20 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va yyjson_val* red = yyjson_obj_get(color, "red"); if (red) ffOptionParseColor(yyjson_get_str(red), &options->tempColorRed); } + + yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); + if (spaceBeforeUnit) + { + int value; + const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + if (error) return error; + options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + } } else if (ffStrEqualsIgnCase(key, "percent")) { @@ -193,6 +243,20 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va yyjson_val* red = yyjson_obj_get(color, "red"); if (red) ffOptionParseColor(yyjson_get_str(red), &options->percentColorRed); } + + yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); + if (spaceBeforeUnit) + { + int value; + const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + if (error) return error; + options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + } } else if (ffStrEqualsIgnCase(key, "bar")) { @@ -288,6 +352,20 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va yyjson_val* ndigits = yyjson_obj_get(val, "ndigits"); if (ndigits) options->freqNdigits = (int8_t) yyjson_get_int(ndigits); + + yyjson_val* spaceBeforeUnit = yyjson_obj_get(val, "spaceBeforeUnit"); + if (spaceBeforeUnit) + { + int value; + const char* error = ffJsonConfigParseEnum(spaceBeforeUnit, &value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + if (error) return error; + options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; + } } else return "Unknown display property"; @@ -403,6 +481,23 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key fprintf(stderr, "--binary-prefix has been renamed to --size-binary-prefix\n"); exit(477); } + else if(ffStrStartsWithIgnCase(key, "--duration-")) + { + const char* subkey = key + strlen("--duration-"); + if(ffStrEqualsIgnCase(subkey, "abbreviation")) + options->durationAbbreviation = ffOptionParseBoolean(value); + else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) + { + options->durationSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + } + else + return false; + } else if(ffStrStartsWithIgnCase(key, "--size-")) { const char* subkey = key + strlen("--size-"); @@ -432,6 +527,15 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key {} }); } + else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) + { + options->sizeSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + } else return false; } @@ -460,6 +564,15 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key ffOptionParseColor(value, &options->tempColorYellow); else if(ffStrEqualsIgnCase(subkey, "color-red")) ffOptionParseColor(value, &options->tempColorRed); + else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) + { + options->tempSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + } else return false; } @@ -476,6 +589,15 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key ffOptionParseColor(value, &options->percentColorYellow); else if(ffStrEqualsIgnCase(subkey, "color-red")) ffOptionParseColor(value, &options->percentColorRed); + else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) + { + options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + } else return false; } @@ -504,6 +626,15 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key const char* subkey = key + strlen("--freq-"); if(ffStrEqualsIgnCase(subkey, "ndigits")) options->freqNdigits = (int8_t) ffOptionParseInt32(key, value); + else if(ffStrEqualsIgnCase(subkey, "space-before-unit")) + { + options->freqSpaceBeforeUnit = (FFSpaceBeforeUnitType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) { + { "default", FF_SPACE_BEFORE_UNIT_DEFAULT }, + { "always", FF_SPACE_BEFORE_UNIT_ALWAYS }, + { "never", FF_SPACE_BEFORE_UNIT_NEVER }, + {}, + }); + } else return false; } @@ -531,10 +662,13 @@ void ffOptionsInitDisplay(FFOptionsDisplay* options) options->debugMode = false; #endif + options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->hideCursor = false; options->sizeBinaryPrefix = FF_SIZE_BINARY_PREFIX_TYPE_IEC; options->sizeNdigits = 2; options->sizeMaxPrefix = UINT8_MAX; + options->sizeSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; + options->stat = -1; options->noBuffer = false; options->keyWidth = 0; @@ -546,18 +680,24 @@ void ffOptionsInitDisplay(FFOptionsDisplay* options) ffStrbufInitStatic(&options->tempColorGreen, FF_COLOR_FG_GREEN); ffStrbufInitStatic(&options->tempColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW); ffStrbufInitStatic(&options->tempColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED); + options->tempSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; ffStrbufInitStatic(&options->barCharElapsed, "■"); ffStrbufInitStatic(&options->barCharTotal, "-"); ffStrbufInitStatic(&options->barBorderLeft, "[ "); ffStrbufInitStatic(&options->barBorderRight, " ]"); options->barWidth = 10; + options->durationAbbreviation = false; + options->durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->percentType = 9; options->percentNdigits = 0; ffStrbufInitStatic(&options->percentColorGreen, FF_COLOR_FG_GREEN); ffStrbufInitStatic(&options->percentColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW); ffStrbufInitStatic(&options->percentColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED); + options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; + options->freqNdigits = 2; + options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; options->fractionNdigits = -1; ffListInit(&options->constants, sizeof(FFstrbuf)); diff --git a/src/options/display.h b/src/options/display.h index d531dd7bc..cc516e877 100644 --- a/src/options/display.h +++ b/src/options/display.h @@ -17,6 +17,13 @@ typedef enum __attribute__((__packed__)) FFTemperatureUnit FF_TEMPERATURE_UNIT_KELVIN, } FFTemperatureUnit; +typedef enum __attribute__((__packed__)) FFSpaceBeforeUnitType +{ + FF_SPACE_BEFORE_UNIT_DEFAULT, + FF_SPACE_BEFORE_UNIT_ALWAYS, + FF_SPACE_BEFORE_UNIT_NEVER, +} FFSpaceBeforeUnitType; + typedef struct FFOptionsDisplay { //If one of those is empty, ffLogoPrint will set them @@ -36,15 +43,19 @@ typedef struct FFOptionsDisplay bool debugMode; #endif bool disableLinewrap; + bool durationAbbreviation; + FFSpaceBeforeUnitType durationSpaceBeforeUnit; bool hideCursor; FFSizeBinaryPrefixType sizeBinaryPrefix; uint8_t sizeNdigits; uint8_t sizeMaxPrefix; + FFSpaceBeforeUnitType sizeSpaceBeforeUnit; FFTemperatureUnit tempUnit; uint8_t tempNdigits; FFstrbuf tempColorGreen; FFstrbuf tempColorYellow; FFstrbuf tempColorRed; + FFSpaceBeforeUnitType tempSpaceBeforeUnit; FFstrbuf barCharElapsed; FFstrbuf barCharTotal; FFstrbuf barBorderLeft; @@ -55,12 +66,15 @@ typedef struct FFOptionsDisplay FFstrbuf percentColorGreen; FFstrbuf percentColorYellow; FFstrbuf percentColorRed; + FFSpaceBeforeUnitType percentSpaceBeforeUnit; bool noBuffer; FFModuleKeyType keyType; uint16_t keyWidth; uint16_t keyPaddingLeft; int8_t freqNdigits; + FFSpaceBeforeUnitType freqSpaceBeforeUnit; int8_t fractionNdigits; + FFlist constants; // list of FFstrbuf } FFOptionsDisplay; diff --git a/tests/duration.c b/tests/duration.c index 0e5c9fb07..283b31801 100644 --- a/tests/duration.c +++ b/tests/duration.c @@ -1,4 +1,4 @@ -#include "common/parsing.h" +#include "common/duration.h" #include "util/textModifier.h" #include "fastfetch.h" @@ -7,7 +7,7 @@ static void verify(uint64_t totalSeconds, const char* expected, int lineNo) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); - ffParseDuration(totalSeconds, &result); + ffDurationAppendNum(totalSeconds, &result); if (!ffStrbufEqualS(&result, expected)) { fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %llu: expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, (unsigned long long) totalSeconds, expected, result.chars); @@ -66,6 +66,55 @@ int main(void) VERIFY(60 * 60 * 24 * 100, "100 days(!)"); VERIFY(60 * 60 * 24 * 200, "200 days(!)"); + instance.config.display.durationAbbreviation = true; + instance.config.display.durationSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_NEVER; + // Test seconds less than 60 + VERIFY(0, "0secs"); + VERIFY(1, "1sec"); + VERIFY(2, "2secs"); + VERIFY(59, "59secs"); + + // Test minute rounding (when seconds >= 30) + VERIFY(60, "1m"); + VERIFY(60 + 29, "1m"); + VERIFY(60 + 30, "2m"); + + // Test only minutes + VERIFY(60 * 2 - 1, "2m"); + VERIFY(60 * 2, "2m"); + VERIFY(60 * 59 + 29, "59m"); + + // Test only hours (no minutes) + VERIFY(60 * 59 + 30, "1h"); + VERIFY(60 * 60, "1h"); + VERIFY(2 * 60 * 60, "2h"); + VERIFY(23 * 60 * 60, "23h"); + + // Test combination of hours and minutes + VERIFY(60 * 60 + 60, "1h 1m"); + VERIFY(60 * 60 + 60 * 2, "1h 2m"); + VERIFY(60 * 60 * 2 + 60 + 29, "2h 1m"); + VERIFY(60 * 60 * 2 + 60 + 30, "2h 2m"); + + // Test days + VERIFY(60 * 60 * 24, "1d"); + VERIFY(60 * 60 * 24 - 1, "1d"); + VERIFY(60 * 60 * 24 * 2, "2d"); + + // Test combination of days and hours + VERIFY(60 * 60 * 24 + 60 * 60, "1d 1h"); + VERIFY(60 * 60 * 24 * 2 + 60 * 60, "2d 1h"); + VERIFY(60 * 60 * 24 * 2 + 60 * 60 * 2, "2d 2h"); + + // Test combination of days, hours, and minutes + VERIFY(60 * 60 * 24 + 60 * 60 + 60, "1d 1h 1m"); + VERIFY(60 * 60 * 24 * 2 + 60 * 60 + 60 * 2, "2d 1h 2m"); + VERIFY(60 * 60 * 24 * 2 + 60 * 2, "2d 2m"); + + // Test very large number of days + VERIFY(60 * 60 * 24 * 100, "100d"); + VERIFY(60 * 60 * 24 * 200, "200d"); + //Success puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET); } From ce554dbdd05d363ff02b04e7c007bde1add0dbbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 30 Jul 2025 14:44:19 +0800 Subject: [PATCH 35/41] Uptime / Battery: add formatted duration string in custom format --- src/detection/battery/battery.h | 2 +- src/modules/battery/battery.c | 6 ++++++ src/modules/uptime/uptime.c | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/detection/battery/battery.h b/src/detection/battery/battery.h index d31a7ef71..94aa65e32 100644 --- a/src/detection/battery/battery.h +++ b/src/detection/battery/battery.h @@ -15,7 +15,7 @@ typedef struct FFBatteryResult double capacity; double temperature; uint32_t cycleCount; - int32_t timeRemaining; + int32_t timeRemaining; // in seconds, -1 if unknown } FFBatteryResult; const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results); diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index b45995b35..e871c3425 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -101,6 +101,9 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin ffPercentAppendBar(&capacityBar, result->capacity, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY tempStr = ffStrbufCreate(); ffTempsAppendNum(result->temperature, &tempStr, options->tempConfig, &options->moduleArgs); + FF_STRBUF_AUTO_DESTROY timeStr = ffStrbufCreate(); + if (result->timeRemaining > 0) + ffDurationAppendNum((uint32_t) result->timeRemaining, &timeStr); FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_FORMAT_ARG(result->manufacturer, "manufacturer"), @@ -117,6 +120,7 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin FF_FORMAT_ARG(hours, "time-hours"), FF_FORMAT_ARG(minutes, "time-minutes"), FF_FORMAT_ARG(seconds, "time-seconds"), + FF_FORMAT_ARG(result->timeRemaining, "time-formatted"), })); } } @@ -254,6 +258,8 @@ void ffGenerateBatteryJsonResult(FFBatteryOptions* options, yyjson_mut_doc* doc, yyjson_mut_obj_add_uint(doc, obj, "cycleCount", battery->cycleCount); if (battery->timeRemaining > 0) yyjson_mut_obj_add_int(doc, obj, "timeRemaining", battery->timeRemaining); + else + yyjson_mut_obj_add_null(doc, obj, "timeRemaining"); } FF_LIST_FOR_EACH(FFBatteryResult, battery, results) diff --git a/src/modules/uptime/uptime.c b/src/modules/uptime/uptime.c index c0118686a..abfbd02ab 100644 --- a/src/modules/uptime/uptime.c +++ b/src/modules/uptime/uptime.c @@ -19,13 +19,12 @@ void ffPrintUptime(FFUptimeOptions* options) } uint64_t uptime = result.uptime; + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + ffDurationAppendNum((uptime + 500) / 1000, &buffer); if(options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_UPTIME_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); - FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - ffDurationAppendNum((uptime + 500) / 1000, &buffer); - ffStrbufPutTo(&buffer, stdout); } else @@ -52,6 +51,7 @@ void ffPrintUptime(FFUptimeOptions* options) FF_FORMAT_ARG(age.years, "years"), FF_FORMAT_ARG(age.daysOfYear, "days-of-year"), FF_FORMAT_ARG(age.yearsFraction, "years-fraction"), + FF_FORMAT_ARG(buffer, "formatted") })); } } From aa0bbd5aeefa400fff1a70e768fde0e496954a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 30 Jul 2025 15:06:16 +0800 Subject: [PATCH 36/41] Percent: add option `--percent-width` --- doc/json_schema.json | 6 ++++++ src/common/percent.c | 2 +- src/data/help.json | 9 +++++++++ src/options/display.c | 6 ++++++ src/options/display.h | 1 + 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index ffc056d75..25c3f47ad 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -1064,6 +1064,12 @@ }, "spaceBeforeUnit": { "$ref": "#/$defs/spaceBeforeUnit" + }, + "width": { + "type": "integer", + "description": "Set the width of the percentage number, in number of characters", + "minimum": 0, + "default": 0 } } }, diff --git a/src/common/percent.c b/src/common/percent.c index 8e4a0c24d..746556183 100644 --- a/src/common/percent.c +++ b/src/common/percent.c @@ -186,7 +186,7 @@ void ffPercentAppendNum(FFstrbuf* buffer, double percent, FFPercentageModuleConf ffStrbufAppendF(buffer, "\e[%sm", colorGreen); } } - ffStrbufAppendF(buffer, "%.*f%s%%", options->percentNdigits, percent, + ffStrbufAppendF(buffer, "%*.*f%s%%", options->percentWidth, options->percentNdigits, percent, options->percentSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : ""); if (colored && !options->pipe) diff --git a/src/data/help.json b/src/data/help.json index 552affcca..59f16be66 100644 --- a/src/data/help.json +++ b/src/data/help.json @@ -658,6 +658,15 @@ } } }, + { + "long": "percent-width", + "desc": "Specify the width of the percentage number, in number of characters", + "remark": "This option affects only percentage numbers, not bars", + "arg": { + "type": "num", + "default": 0 + } + }, { "long": "bar-char-elapsed", "desc": "Set the character to use in the elapsed part of percentage bars", diff --git a/src/options/display.c b/src/options/display.c index 5eb1e33e5..eae73b1c9 100644 --- a/src/options/display.c +++ b/src/options/display.c @@ -257,6 +257,9 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va if (error) return error; options->percentSpaceBeforeUnit = (FFSpaceBeforeUnitType) value; } + + yyjson_val* width = yyjson_obj_get(val, "width"); + if (width) options->percentWidth = (uint8_t) yyjson_get_uint(width); } else if (ffStrEqualsIgnCase(key, "bar")) { @@ -598,6 +601,8 @@ bool ffOptionsParseDisplayCommandLine(FFOptionsDisplay* options, const char* key {}, }); } + else if(ffStrEqualsIgnCase(subkey, "width")) + options->percentWidth = (uint8_t) ffOptionParseUInt32(key, value); else return false; } @@ -695,6 +700,7 @@ void ffOptionsInitDisplay(FFOptionsDisplay* options) ffStrbufInitStatic(&options->percentColorYellow, instance.state.terminalLightTheme ? FF_COLOR_FG_YELLOW : FF_COLOR_FG_LIGHT_YELLOW); ffStrbufInitStatic(&options->percentColorRed, instance.state.terminalLightTheme ? FF_COLOR_FG_RED : FF_COLOR_FG_LIGHT_RED); options->percentSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; + options->percentWidth = 0; options->freqNdigits = 2; options->freqSpaceBeforeUnit = FF_SPACE_BEFORE_UNIT_DEFAULT; diff --git a/src/options/display.h b/src/options/display.h index cc516e877..6a50b848c 100644 --- a/src/options/display.h +++ b/src/options/display.h @@ -67,6 +67,7 @@ typedef struct FFOptionsDisplay FFstrbuf percentColorYellow; FFstrbuf percentColorRed; FFSpaceBeforeUnitType percentSpaceBeforeUnit; + uint8_t percentWidth; bool noBuffer; FFModuleKeyType keyType; uint16_t keyWidth; From d0928a63bfc5a6eb64413ef0b57bf882dd4bfc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 30 Jul 2025 16:18:04 +0800 Subject: [PATCH 37/41] Doc: update readme --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45031341b..a98b49493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,35 @@ +# 2.49.0 + +Changes: +* Due to more restricted permissions of macOS Tahoe, SSID detection on macOS 26+ requires root privileges. `` will be displayed otherwise. + +Features: +* Improve `nouveau` driver support for `--gpu-driver-specific` (GPU, Linux) + * VRAM size detection + * GPU temperature detection + * Core count detection +* Improve scoop package manager detection (Packages, Windows) + * Support `scoop-global` + * Read scoop's config file to find the installation path of scoop +* Detect more Snapdragon SOCs (CPU, Android) +* + +Bugfixes: +* Improve accuracy of flatpak count detection (#1856, Packages, Linux) +* Remove qi package manager support (#1858, Packages, Linux) +* Improve LocalIP module on Windows (LocalIP, Windows) + * Improve default route detection when multiple network interfaces are connected + * Fix link speed calcuation +* Fix interface status when the interface is up but not connected (Wifi, Linux) +* Fix variable names in custom format + * `full-path` to `path` (Editor) + * `session` to `session-name` (Users) + * `name` to `project-name` (Version) + +Logos: +* Add `Aeon` +* Remove `Evolinx` + # 2.48.1 Features: From 4e05502d7a3f2e88ac6f9172033ee47ff19a55ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 31 Jul 2025 10:17:37 +0800 Subject: [PATCH 38/41] Release: v2.49.0 --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++------------ CMakeLists.txt | 2 +- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a98b49493..4c9cd1bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,46 @@ # 2.49.0 +Deprecation Notice: +* In fastfetch v2, the JSONC configuration format has been introduced, while command line configuration flags are kept for compatibility. Although they have the same effects, they use two different code paths, and as the number of flags grows, the codebase is becoming increasingly difficult to maintain. + * Removal of module config flags is planned for **v2.50.0**, which will also fix a long-standing issue #1477. + * Removal of most other config flags is also planned for later versions. +* Keys of JSON configuration files will be all case-sensitive. Currently they are inconsistent. Planned for **v2.50.0**. + Changes: -* Due to more restricted permissions of macOS Tahoe, SSID detection on macOS 26+ requires root privileges. `` will be displayed otherwise. +* Due to more restrictive permissions in macOS Tahoe, SSID detection on macOS 26+ requires root privileges. `` will be displayed otherwise. Features: * Improve `nouveau` driver support for `--gpu-driver-specific` (GPU, Linux) * VRAM size detection * GPU temperature detection - * Core count detection -* Improve scoop package manager detection (Packages, Windows) - * Support `scoop-global` - * Read scoop's config file to find the installation path of scoop -* Detect more Snapdragon SOCs (CPU, Android) -* + * Core count detection (when available) +* Improve Scoop package manager detection (Packages, Windows) + * Support [`scoop-global`](https://github.com/ScoopInstaller/Install?tab=readme-ov-file#advanced-installation) + * Read Scoop's config file to find the installation path of Scoop +* Improve ARM SoC detection (CPU, Android) + * Make SoC detection more lenient. Higher chance to match at the cost of accuracy. + * Add more Snapdragon SoC names +* Support labwc WM version detection, used for XFCE4 on Wayland (WM, Linux) +* Improve accuracy of GPU temperature detection for Intel dedicated GPUs on Windows (GPU, Windows) +* Parse unicode escaped strings generated by qt5ct (#1864, Font, Linux) +* Add `--{duration,percent,size,freq,temp}-space-before-unit [always|never]` options to add a space before the unit when printing duration, percent, size, frequency and temperature values +* Add `--duration-abbreviation` to abbreviate duration values in custom format + * For example: `1 day, 2 hours, 3 mins` will be displayed as `1d 2h 3m` +* Add `--percent-with` to pad the percent value with spaces to a fixed width + * For example: `--percent-with 3` will display ` 50%` instead of `50%`; useful for aligning percent values in custom format Bugfixes: -* Improve accuracy of flatpak count detection (#1856, Packages, Linux) +* Improve accuracy of Flatpak count detection (#1856, Packages, Linux) * Remove qi package manager support (#1858, Packages, Linux) -* Improve LocalIP module on Windows (LocalIP, Windows) - * Improve default route detection when multiple network interfaces are connected - * Fix link speed calcuation +* Fix LocalIP module on Windows (LocalIP, Windows) + * Fix default route detection when multiple network interfaces are connected + * Fix link speed calculation * Fix interface status when the interface is up but not connected (Wifi, Linux) -* Fix variable names in custom format +* Fix variable names in custom format (#1861) * `full-path` to `path` (Editor) * `session` to `session-name` (Users) * `name` to `project-name` (Version) +* Fix wrong /s assignment in custom format (#1871, DiskIO) Logos: * Add `Aeon` diff --git a/CMakeLists.txt b/CMakeLists.txt index f66e830c8..22917aec6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url project(fastfetch - VERSION 2.48.1 + VERSION 2.49.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" From 38c0b24daaaf08e86f2edc4db1b5a92516de060e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 31 Jul 2025 10:50:45 +0800 Subject: [PATCH 39/41] WM (Linux): fix a copy-paste error. --- src/detection/wm/wm_linux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detection/wm/wm_linux.c b/src/detection/wm/wm_linux.c index 4743bc252..6e7cc9785 100644 --- a/src/detection/wm/wm_linux.c +++ b/src/detection/wm/wm_linux.c @@ -275,7 +275,7 @@ static const char* getLabwc(FFstrbuf* result) return NULL; } - return "Failed to run command `openbox --version`"; + return "Failed to run command `labwc --version`"; } const char* ffDetectWMVersion(const FFstrbuf* wmName, FFstrbuf* result, FF_MAYBE_UNUSED FFWMOptions* options) From 677e920dc8a70166b42f6f9a0651f712b9b9f20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 31 Jul 2025 10:51:53 +0800 Subject: [PATCH 40/41] Battery: address copilot feedback --- src/modules/battery/battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index e871c3425..b51bbbd60 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -120,7 +120,7 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin FF_FORMAT_ARG(hours, "time-hours"), FF_FORMAT_ARG(minutes, "time-minutes"), FF_FORMAT_ARG(seconds, "time-seconds"), - FF_FORMAT_ARG(result->timeRemaining, "time-formatted"), + FF_FORMAT_ARG(timeStr, "time-formatted"), })); } } From eca3625165b227c09e410eb988691193cc99a757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 31 Jul 2025 13:32:18 +0800 Subject: [PATCH 41/41] Battery / Uptime: update document of custom format --- doc/json_schema.json | 6 +++--- src/modules/battery/battery.c | 1 + src/modules/uptime/uptime.c | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index 25c3f47ad..3805a71fe 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -253,7 +253,7 @@ "batteryFormat": { - "description": "Output format of the module `Battery`. See Wiki for formatting syntax\n 1. {manufacturer}: Battery manufacturer\n 2. {model-name}: Battery model name\n 3. {technology}: Battery technology\n 4. {capacity}: Battery capacity (percentage num)\n 5. {status}: Battery status\n 6. {temperature}: Battery temperature (formatted)\n 7. {cycle-count}: Battery cycle count\n 8. {serial}: Battery serial number\n 9. {manufacture-date}: Battery manufactor date\n 10. {capacity-bar}: Battery capacity (percentage bar)\n 11. {time-days}: Battery time remaining days\n 12. {time-hours}: Battery time remaining hours\n 13. {time-minutes}: Battery time remaining minutes\n 14. {time-seconds}: Battery time remaining seconds", + "description": "Output format of the module `Battery`. See Wiki for formatting syntax\n 1. {manufacturer}: Battery manufacturer\n 2. {model-name}: Battery model name\n 3. {technology}: Battery technology\n 4. {capacity}: Battery capacity (percentage num)\n 5. {status}: Battery status\n 6. {temperature}: Battery temperature (formatted)\n 7. {cycle-count}: Battery cycle count\n 8. {serial}: Battery serial number\n 9. {manufacture-date}: Battery manufactor date\n 10. {capacity-bar}: Battery capacity (percentage bar)\n 11. {time-days}: Battery time remaining days\n 12. {time-hours}: Battery time remaining hours\n 13. {time-minutes}: Battery time remaining minutes\n 14. {time-seconds}: Battery time remaining seconds\n 15. {time-formatted}: Battery time remaining (formatted)", "type": "string" }, "biosFormat": { @@ -421,7 +421,7 @@ "type": "string" }, "packagesFormat": { - "description": "Output format of the module `Packages`. See Wiki for formatting syntax\n 1. {all}: Number of all packages\n 2. {pacman}: Number of pacman packages\n 3. {pacman-branch}: Pacman branch on manjaro\n 4. {dpkg}: Number of dpkg packages\n 5. {rpm}: Number of rpm packages\n 6. {emerge}: Number of emerge packages\n 7. {eopkg}: Number of eopkg packages\n 8. {xbps}: Number of xbps packages\n 9. {nix-system}: Number of nix-system packages\n 10. {nix-user}: Number of nix-user packages\n 11. {nix-default}: Number of nix-default packages\n 12. {apk}: Number of apk packages\n 13. {pkg}: Number of pkg packages\n 14. {flatpak-system}: Number of flatpak-system app packages\n 15. {flatpak-user}: Number of flatpak-user app packages\n 16. {snap}: Number of snap packages\n 17. {brew}: Number of brew packages\n 18. {brew-cask}: Number of brew-cask packages\n 19. {macports}: Number of macports packages\n 20. {scoop}: Number of scoop packages\n 21. {choco}: Number of choco packages\n 22. {pkgtool}: Number of pkgtool packages\n 23. {paludis}: Number of paludis packages\n 24. {winget}: Number of winget packages\n 25. {opkg}: Number of opkg packages\n 26. {am-system}: Number of am-system packages\n 27. {sorcery}: Number of sorcery packages\n 28. {lpkg}: Number of lpkg packages\n 29. {lpkgbuild}: Number of lpkgbuild packages\n 30. {guix-system}: Number of guix-system packages\n 31. {guix-user}: Number of guix-user packages\n 32. {guix-home}: Number of guix-home packages\n 33. {linglong}: Number of linglong packages\n 34. {pacstall}: Number of pacstall packages\n 35. {mport}: Number of mport packages\n 36. {am-user}: Number of am-user (aka appman) packages\n 37. {pkgsrc}: Number of pkgsrc packages\n 38. {hpkg-system}: Number of hpkg-system packages\n 39. {hpkg-user}: Number of hpkg-user packages\n 40. {pisi}: Number of pisi packages\n 41. {soar}: Number of soar packages\n 42. {nix-all}: Total number of all nix packages\n 43. {flatpak-all}: Total number of all flatpak app packages\n 44. {brew-all}: Total number of all brew packages\n 45. {guix-all}: Total number of all guix packages\n 46. {hpkg-all}: Total number of all hpkg packages", + "description": "Output format of the module `Packages`. See Wiki for formatting syntax\n 1. {all}: Number of all packages\n 2. {pacman}: Number of pacman packages\n 3. {pacman-branch}: Pacman branch on manjaro\n 4. {dpkg}: Number of dpkg packages\n 5. {rpm}: Number of rpm packages\n 6. {emerge}: Number of emerge packages\n 7. {eopkg}: Number of eopkg packages\n 8. {xbps}: Number of xbps packages\n 9. {nix-system}: Number of nix-system packages\n 10. {nix-user}: Number of nix-user packages\n 11. {nix-default}: Number of nix-default packages\n 12. {apk}: Number of apk packages\n 13. {pkg}: Number of pkg packages\n 14. {flatpak-system}: Number of flatpak-system app packages\n 15. {flatpak-user}: Number of flatpak-user app packages\n 16. {snap}: Number of snap packages\n 17. {brew}: Number of brew packages\n 18. {brew-cask}: Number of brew-cask packages\n 19. {macports}: Number of macports packages\n 20. {scoop-user}: Number of scoop-user packages\n 21. {scoop-global}: Number of scoop-global packages\n 22. {choco}: Number of choco packages\n 23. {pkgtool}: Number of pkgtool packages\n 24. {paludis}: Number of paludis packages\n 25. {winget}: Number of winget packages\n 26. {opkg}: Number of opkg packages\n 27. {am-system}: Number of am-system packages\n 28. {sorcery}: Number of sorcery packages\n 29. {lpkg}: Number of lpkg packages\n 30. {lpkgbuild}: Number of lpkgbuild packages\n 31. {guix-system}: Number of guix-system packages\n 32. {guix-user}: Number of guix-user packages\n 33. {guix-home}: Number of guix-home packages\n 34. {linglong}: Number of linglong packages\n 35. {pacstall}: Number of pacstall packages\n 36. {mport}: Number of mport packages\n 37. {am-user}: Number of am-user (aka appman) packages\n 38. {pkgsrc}: Number of pkgsrc packages\n 39. {hpkg-system}: Number of hpkg-system packages\n 40. {hpkg-user}: Number of hpkg-user packages\n 41. {pisi}: Number of pisi packages\n 42. {soar}: Number of soar packages\n 43. {nix-all}: Total number of all nix packages\n 44. {flatpak-all}: Total number of all flatpak app packages\n 45. {brew-all}: Total number of all brew packages\n 46. {guix-all}: Total number of all guix packages\n 47. {hpkg-all}: Total number of all hpkg packages", "type": "string" }, "physicaldiskFormat": { @@ -489,7 +489,7 @@ "type": "string" }, "uptimeFormat": { - "description": "Output format of the module `Uptime`. See Wiki for formatting syntax\n 1. {days}: Days after boot\n 2. {hours}: Hours after boot\n 3. {minutes}: Minutes after boot\n 4. {seconds}: Seconds after boot\n 5. {milliseconds}: Milliseconds after boot\n 6. {boot-time}: Boot time in local timezone\n 7. {years}: Years integer after boot\n 8. {days-of-year}: Days of year after boot\n 9. {years-fraction}: Years fraction after boot", + "description": "Output format of the module `Uptime`. See Wiki for formatting syntax\n 1. {days}: Days after boot\n 2. {hours}: Hours after boot\n 3. {minutes}: Minutes after boot\n 4. {seconds}: Seconds after boot\n 5. {milliseconds}: Milliseconds after boot\n 6. {boot-time}: Boot time in local timezone\n 7. {years}: Years integer after boot\n 8. {days-of-year}: Days of year after boot\n 9. {years-fraction}: Years fraction after boot\n 10. {formatted}: Formatted uptime", "type": "string" }, "usersFormat": { diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index b51bbbd60..f1afddbb1 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -296,6 +296,7 @@ static FFModuleBaseInfo ffModuleInfo = { {"Battery time remaining hours", "time-hours"}, {"Battery time remaining minutes", "time-minutes"}, {"Battery time remaining seconds", "time-seconds"}, + {"Battery time remaining (formatted)", "time-formatted"}, })) }; diff --git a/src/modules/uptime/uptime.c b/src/modules/uptime/uptime.c index abfbd02ab..74d985f85 100644 --- a/src/modules/uptime/uptime.c +++ b/src/modules/uptime/uptime.c @@ -125,6 +125,7 @@ static FFModuleBaseInfo ffModuleInfo = { {"Years integer after boot", "years"}, {"Days of year after boot", "days-of-year"}, {"Years fraction after boot", "years-fraction"}, + {"Formatted uptime", "formatted"}, })) };