diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83e824db63..71629f5fc6 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 @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 45031341bd..4c9cd1bd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,51 @@ +# 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 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 (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) +* Remove qi package manager support (#1858, Packages, Linux) +* 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 (#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` +* Remove `Evolinx` + # 2.48.1 Features: diff --git a/CMakeLists.txt b/CMakeLists.txt index e008886a77..22917aec6c 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" @@ -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 a6a8165d49..3805a71fe1 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" }, @@ -230,10 +232,28 @@ } } }, + "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": { - "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": { @@ -317,7 +337,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": { @@ -401,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. {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-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": { @@ -469,15 +489,15 @@ "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": { - "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": { - "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": { @@ -921,6 +941,9 @@ "minimum": 0, "maximum": 9, "default": 2 + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" } } }, @@ -963,6 +986,9 @@ "default": "light_red" } } + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" } } }, @@ -1035,6 +1061,15 @@ "default": "light_red" } } + }, + "spaceBeforeUnit": { + "$ref": "#/$defs/spaceBeforeUnit" + }, + "width": { + "type": "integer", + "description": "Set the width of the percentage number, in number of characters", + "minimum": 0, + "default": 0 } } }, @@ -1049,6 +1084,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" } } }, @@ -1959,7 +2011,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/presets/examples/25.jsonc b/presets/examples/25.jsonc index 85f4835f75..5d0ce75bb9 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 +*/ diff --git a/src/common/duration.c b/src/common/duration.c new file mode 100644 index 0000000000..16e5570e9b --- /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 0000000000..234a2c9762 --- /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 0000000000..248bb06387 --- /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 0000000000..53dacf8039 --- /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/io/io.h b/src/common/io/io.h index 4eabaed04d..c783e5323c 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/common/netif/netif.c b/src/common/netif/netif.c index da5a838594..8d02b1a542 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 23ca5ef607..40c97948d2 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 6042dd8d6e..0dfd2bd077 100644 --- a/src/common/netif/netif_windows.c +++ b/src/common/netif/netif_windows.c @@ -4,15 +4,15 @@ #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) { @@ -24,9 +24,31 @@ bool ffNetifGetDefaultRouteImpl(FF_MAYBE_UNUSED char iface[IF_NAMESIZE + 1], uin (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; + } + } } } diff --git a/src/common/parsing.c b/src/common/parsing.c index 7aeea191e8..00de9a8d50 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 af72f441d7..f189879069 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 8a1dffc147..7465561837 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->percentWidth, 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 0000000000..f32b021812 --- /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 0000000000..f234d5ae09 --- /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 10e11de61e..3e1e59c543 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 dbb27f5ace..59f16be662 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,27 @@ "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": "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", @@ -711,6 +755,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 +775,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 +844,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": [ @@ -854,7 +934,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" } }, { diff --git a/src/detection/battery/battery.h b/src/detection/battery/battery.h index d31a7ef71d..94aa65e324 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/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c index 012af9202d..46ab37b788 100644 --- a/src/detection/cpu/cpu_linux.c +++ b/src/detection/cpu/cpu_linux.c @@ -100,85 +100,75 @@ static void detectQualcomm(FFCPUResult* cpu) { // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips - 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]"); - - 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]"); - - 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]"); + uint32_t code = (uint32_t) strtoul(cpu->name.chars + 2, NULL, 10); + const char* name = NULL; + + 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; + } + + 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) diff --git a/src/detection/gpu/gpu.h b/src/detection/gpu/gpu.h index 1ebee2de6c..e4db0f8588 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_apple.m b/src/detection/gpu/gpu_apple.m index b4937fbf3f..e206bdc1a5 100644 --- a/src/detection/gpu/gpu_apple.m +++ b/src/detection/gpu/gpu_apple.m @@ -60,10 +60,13 @@ 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]) diff --git a/src/detection/gpu/gpu_bsd.c b/src/detection/gpu/gpu_bsd.c index 3d46055460..b4c2636998 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 5ddea2799f..a1c9cd0c30 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_intel.c b/src/detection/gpu/gpu_intel.c index 59f2689302..f5fe08cd57 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/gpu_linux.c b/src/detection/gpu/gpu_linux.c index c37502e47a..a43018e35f 100644 --- a/src/detection/gpu/gpu_linux.c +++ b/src/detection/gpu/gpu_linux.c @@ -263,6 +263,49 @@ 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 + 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 +412,12 @@ 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")) + { + pciDetectNouveauSpecific(options, gpu, deviceDir, buffer); + if (options->driverSpecific && drmKey) + drmDetectNouveauSpecific(gpu, drmKey, buffer); + } else { ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { diff --git a/src/detection/gpu/igcl.h b/src/detection/gpu/igcl.h index 60e5cf22a2..8e8ff6f962 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; diff --git a/src/detection/gtk_qt/qt.c b/src/detection/gtk_qt/qt.c index ad2c0ab2d8..b27ad748ff 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")) { diff --git a/src/detection/localip/localip.h b/src/detection/localip/localip.h index 11ac50e558..a3fde0f1cc 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 7d50916997..1b067885df 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; @@ -170,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) diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h index ad7dd93414..e96b0762c5 100644 --- a/src/detection/packages/packages.h +++ b/src/detection/packages/packages.h @@ -36,9 +36,9 @@ typedef struct FFPackagesResult uint32_t pkg; uint32_t pkgsrc; uint32_t pkgtool; - uint32_t qi; 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_linux.c b/src/detection/packages/packages_linux.c index 19543a8754..9fdb294850 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); @@ -576,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/detection/packages/packages_windows.c b/src/detection/packages/packages_windows.c index a9fa9df6d7..f4a497dd45 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 @@ -33,22 +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"); - const char* scoopEnv = getenv("SCOOP"); - if(ffStrSet(scoopEnv)) + yyjson_val* root = NULL; + + yyjson_doc* __attribute__((__cleanup__(wrapYyjsonFree))) doc = yyjson_read_file(scoopPath.chars, 0, NULL, NULL); + if (doc) { - ffStrbufAppendS(&scoopPath, scoopEnv); + 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/detection/wifi/wifi_apple.m b/src/detection/wifi/wifi_apple.m index d6377908c7..1792bb8517 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 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb 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 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb 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 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb 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 @@ static bool getWifiInfoByIpconfig(FFstrbuf* ipconfig, const char* prefix, FFstrb 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); +} diff --git a/src/detection/wifi/wifi_linux.c b/src/detection/wifi/wifi_linux.c index deadf6dccd..34c3d9f084 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) { diff --git a/src/detection/wm/wm_linux.c b/src/detection/wm/wm_linux.c index 9b9956840b..6e7cc97852 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 `labwc --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"; } diff --git a/src/logo/ascii/aeon.txt b/src/logo/ascii/aeon.txt new file mode 100644 index 0000000000..902af06da5 --- /dev/null +++ b/src/logo/ascii/aeon.txt @@ -0,0 +1,14 @@ +⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷ +⣿⡇ ⢸⣿ +⣿⡇ ⢀⣀ ⣀⡀ ⢸⣿ +⣿⣇ ⠸⣿⣄ ⣠⣿⠇ ⣸⣿ +⢹⣿⡄ ⠙⠻⠿⠿⠟⠋ ⢠⣿⡏ + ⠹⣿⣦⡀ ⢀⣴⣿⠏ + ⠈⠛⢿⣶⣤⣄ ⣠⣤⣶⡿⠛⠁ +$2 ⣠⣴⡿⠿⠛ ⠛⠿⢿⣦⣄ + ⣠⣾⠟⠉ ⠉⠻⣷⣄ +⢰⣿⠏ ⢀⣤⣶⣶⣤⡀ ⠹⣿⡆ +⣿⡟ ⢰⣿⠏⠁⠈⠹⣿⡆ ⢿⣿ +⣿⡇ ⠈⠋ ⠙⠁ ⢸⣿ +⣿⡇ ⢸⣿ +⣿⣷⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣾⣿ \ No newline at end of file diff --git a/src/logo/ascii/evolinx.txt b/src/logo/ascii/evolinx.txt deleted file mode 100644 index 6aa225471d..0000000000 --- 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 3ea22dfd46..35eabdb65e 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"}, @@ -1614,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"}, diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index ba2327e341..f1afddbb15 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)"); } } @@ -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(timeStr, "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) @@ -290,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/btrfs/btrfs.c b/src/modules/btrfs/btrfs.c index 02ebf60324..31b58e1f30 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 178574aa0b..2511a6b04d 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 2be8f4551f..64eaa06124 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 b840124436..1596b3861a 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 08e0ca84e4..19af8eb3c9 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,22 +57,22 @@ 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 { - ffStrbufClear(&buffer2); - ffParseSize(dev->bytesRead, &buffer); - if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); - ffParseSize(dev->bytesWritten, &buffer2); + ffSizeAppendNum(dev->bytesRead, &buffer); if (!options->detectTotal) ffStrbufAppendS(&buffer, "/s"); + ffStrbufClear(&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[]){ FF_FORMAT_ARG(buffer, "size-read"), diff --git a/src/modules/display/display.c b/src/modules/display/display.c index a55c15d83f..437bc79f6f 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/editor/editor.c b/src/modules/editor/editor.c index 11afc0940a..42ff06a5ee 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"}, })) }; diff --git a/src/modules/gpu/gpu.c b/src/modules/gpu/gpu.c index 7d917c83df..f2f5806c78 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 44c85668ba..d747e67528 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/localip/localip.c b/src/modules/localip/localip.c index e143fcf3f7..4c98b1d6f6 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); } } diff --git a/src/modules/memory/memory.c b/src/modules/memory/memory.c index 0af03736d4..deac7ef91d 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 d49ca61f9e..5b0fb4bbb4 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/packages/option.h b/src/modules/packages/option.h index 40e5b14137..5709578d44 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 190347d3ec..06c1817a33 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) @@ -135,7 +144,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) @@ -163,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"), @@ -179,7 +188,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 +277,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 +400,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 +468,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,10 +529,10 @@ 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) + 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) @@ -568,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"}, @@ -584,7 +586,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"}, diff --git a/src/modules/physicaldisk/physicaldisk.c b/src/modules/physicaldisk/physicaldisk.c index de67517910..f687706977 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 5ea3573a9b..4bfe28ef46 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 c4a28f67bc..fb3072f44e 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 445de7e1fb..74d985f85d 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" @@ -18,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(); - ffParseDuration((uptime + 500) / 1000, &buffer); - ffStrbufPutTo(&buffer, stdout); } else @@ -51,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") })); } } @@ -124,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"}, })) }; diff --git a/src/modules/users/users.c b/src/modules/users/users.c index abea30aa0e..9ba72977b8 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"}, diff --git a/src/modules/version/version.c b/src/modules/version/version.c index 39a851e326..426cbc3d26 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"}, diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c index cae5150dda..c7038a0fc0 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 a00274e708..151836f0b5 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 cc4b703e65..eae73b1c9b 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,23 @@ 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; + } + + yyjson_val* width = yyjson_obj_get(val, "width"); + if (width) options->percentWidth = (uint8_t) yyjson_get_uint(width); } else if (ffStrEqualsIgnCase(key, "bar")) { @@ -288,6 +355,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 +484,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 +530,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 +567,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 +592,17 @@ 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 if(ffStrEqualsIgnCase(subkey, "width")) + options->percentWidth = (uint8_t) ffOptionParseUInt32(key, value); else return false; } @@ -504,6 +631,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 +667,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 +685,25 @@ 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->percentWidth = 0; + 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 d531dd7bc4..6a50b848cf 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,16 @@ typedef struct FFOptionsDisplay FFstrbuf percentColorGreen; FFstrbuf percentColorYellow; FFstrbuf percentColorRed; + FFSpaceBeforeUnitType percentSpaceBeforeUnit; + uint8_t percentWidth; 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/src/util/FFcheckmacros.h b/src/util/FFcheckmacros.h index 4a6243a72e..00d34b2d3f 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 diff --git a/src/util/FFstrbuf.c b/src/util/FFstrbuf.c index 943d0de82e..6fe0b151c8 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; @@ -661,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 d7fdab3547..7e96927225 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); @@ -95,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/duration.c b/tests/duration.c index 0e5c9fb078..283b318018 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); } diff --git a/tests/strbuf.c b/tests/strbuf.c index 8d2c0131c2..94addc4668 100644 --- a/tests/strbuf.c +++ b/tests/strbuf.c @@ -665,6 +665,61 @@ 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); + } + + { + 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); }