diff --git a/.github/ISSUE_TEMPLATE/logo_request.yml b/.github/ISSUE_TEMPLATE/logo_request.yml index fa0570dab3..d3d605d57e 100644 --- a/.github/ISSUE_TEMPLATE/logo_request.yml +++ b/.github/ISSUE_TEMPLATE/logo_request.yml @@ -6,16 +6,31 @@ body: - type: markdown attributes: value: | - Tip: You can display a logo in fastfetch without adding it to fastfetch's official repo. + > [!WARNING] + > **NB:** If your logo request does not meet the basic requirements outlined in this template, the issue may be closed at any time without any further explanation. + + **Tip:** You can display a logo in fastfetch without adding it to fastfetch's official repo. For highly customized, personal logos, we recommend keeping them locally. - Please refer to https://github.com/fastfetch-cli/fastfetch/wiki/Migrate-Neofetch-Logo-To-Fastfetch + You can test your logo locally using: + `fastfetch -l /path/to/logo.txt --logo-color-1 white --logo-color-2 red` + Please refer to the [Migration Guide](https://github.com/fastfetch-cli/fastfetch/wiki/Migrate-Neofetch-Logo-To-Fastfetch) for details on color placeholders (`$1`, `$2`, etc.). + - type: textarea attributes: label: OS - description: Paste the contents of `/etc/os-release` and `/etc/lsb-release` here. If neither file exists, describe how to identify the distro. + description: Paste the contents of `/etc/os-release` (specifically the `ID` and `PRETTY_NAME` fields). If neither file exists, describe how to identify the distro. placeholder: cat /etc/os-release validations: required: true + + - type: input + attributes: + label: Public repository URL + description: A public repository is required to verify the request. Please provide a link to the repository where the distro is developed. + placeholder: https://github.com/example/distro + validations: + required: true + - type: input attributes: label: Distro Website @@ -23,25 +38,35 @@ body: placeholder: https://example.com validations: required: true + - type: textarea attributes: label: ASCII Art - description: The ASCII logo should not take up too much space (smaller than 50x20 characters, W x H). Please also include the color codes if they are not available in `os-release`. - placeholder: Paste ASCII art here + description: | + The ASCII logo should not take up too much space (smaller than 50x20 characters, W x H). + **Important:** Fastfetch uses `$1`, `$2`, `$3` etc. for color placeholders. Do NOT use hardcoded ANSI escape codes (like `\033[38;2...m`) or Neofetch's `${c1}` syntax. + Take as examples. + placeholder: Paste ASCII art with $1, $2 placeholders here validations: required: true + - type: input attributes: label: Original Image URL description: If the ASCII art is based on an image, please provide a link to the original image file. placeholder: Image URL from the distro website mentioned above + - type: checkboxes attributes: label: Checklist options: + - label: My distro has reached a stable state (out of beta, alpha, RC). + required: true - label: The ASCII art is smaller than 50x20 characters (W x H). required: true - - label: The ASCII art includes color codes, or the color codes are available in `os-release`. + - label: The ASCII art uses `$N` color placeholders (or colors are natively available in `os-release`). + required: true + - label: The ASCII art has no unnecessary trailing spaces at the end of lines. required: true - - label: The ASCII art has no internal padding (spaces at the start and/or end of lines). + - label: I promise that I will send a removal request if the distro is discontinued. required: true diff --git a/.github/workflows/build-freebsd-amd64.yml b/.github/workflows/build-freebsd-amd64.yml index 041bc4ff20..a6af2cba09 100644 --- a/.github/workflows/build-freebsd-amd64.yml +++ b/.github/workflows/build-freebsd-amd64.yml @@ -31,7 +31,7 @@ jobs: - name: Update and install dependencies run: | sudo pkg update - sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa lua54 libva libvdpau + sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3 egl opencl ocl-icd v4l_compat chafa lua54 libva libvdpau - name: CMake configuration run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} . diff --git a/.github/workflows/build-linux-loong64.yml b/.github/workflows/build-linux-loong64.yml new file mode 100644 index 0000000000..25b140d8f4 --- /dev/null +++ b/.github/workflows/build-linux-loong64.yml @@ -0,0 +1,57 @@ +name: Reusable Linux loong64 + +on: + workflow_call: + +env: + CMAKE_BUILD_TYPE: ${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - name: checkout repository + uses: actions/checkout@v7 + + - name: set up QEMU for loong64 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: build in loong64 container + run: | + docker run --rm --platform linux/loong64 \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + lcr.loongnix.cn/debian:14 \ + bash -c ' + set -e + uname -a + cat /etc/os-release || true + + apt-get update + apt-get install -y --no-install-recommends \ + cmake make gcc g++ \ + libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev \ + libdconf-dev libdbus-1-dev libmagickcore-dev \ + libsqlite3-dev librpm-dev \ + libegl-dev libglx-dev ocl-icd-opencl-dev \ + libpulse-dev libdrm-dev \ + libelf-dev libefl-all-dev \ + liblua5.4-dev \ + libvdpau-dev libva-dev \ + rpm + + cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . + cmake --build . --target package --verbose -j$(nproc) + ./fastfetch --list-features + time ./fastfetch -c presets/ci.jsonc --stat false + time ./fastfetch -c presets/ci.jsonc --format json + time ./flashfetch + ldd fastfetch + ctest --output-on-failure + ' + + - name: upload artifacts + uses: actions/upload-artifact@v7 + with: + name: fastfetch-linux-loong64 + path: ./fastfetch-*.* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00d37612df..46ac9d4fab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,15 @@ jobs: uses: ./.github/workflows/build-linux-armv7l.yml secrets: inherit + linux-loong64: + needs: no-features-test + name: Linux-loong64 + permissions: + security-events: write + contents: read + uses: ./.github/workflows/build-linux-loong64.yml + secrets: inherit + linux-vms: needs: no-features-test name: Linux-${{ matrix.arch }} @@ -145,6 +154,7 @@ jobs: secrets: inherit haiku-amd64: + if: false # Disabled because the Haiku build is currently broken needs: no-features-test name: Haiku-amd64 permissions: @@ -188,6 +198,7 @@ jobs: - linux-hosts - linux-i686 - linux-armv7l + - linux-loong64 - linux-vms - musl-amd64 - macos-hosts @@ -197,7 +208,7 @@ jobs: - dragonfly-amd64 - solaris-amd64 - omnios-amd64 - - haiku-amd64 + # - haiku-amd64 - windows-hosts permissions: contents: write diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb47a237d..8ab97abc36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +# 2.67.0 + +Changes: +* The minimum supported compiler version has been raised to GCC 13 (released on 2023-04-26) and Clang 16 (released on 2023-03-17) to accommodate the new C23 syntax used throughout the codebase. (General) + * As a result, building from source in Debian 12 (which only ships GCC 12 by default) is no longer supported. Users must upgrade their compiler or use the prebuilt binaries from the GitHub releases page. +* The `general.preRun` option has been removed due to security concerns. (General) + * Users who relied on this option to run a command before printing output will need to move that logic to an external wrapper. +* The `{status}` placeholder in Battery custom format is now an array type instead of a single comma-separated string. (Battery) + * This is a breaking change for users who print `{status}` in Lua scripts. They can use `table.concat(...)` to join the array into a single string if needed. +* The `--gen-config-*` flags (`--gen-config-full`, `--gen-config-force`, `--gen-config-full-force`) have been removed and their functions have been merged into `--gen-config`. (Global) + * See features below. + +Features: +* `--gen-config` now opens an interactive configuration TUI to make generating config files easier. + * It falls back to non-interactive generation when `$NO_COLOR` is set or when stdin/stdout is not a TTY. This behavior can also be used to disable the interactive mode. +* Added an experimental [WebUI](https://fastfetch-cli.github.io/fastfetch-config/) ([separate project](https://github.com/fastfetch-cli/fastfetch-config)) for generating and editing configs. +* Added `-w`/`--watch` as a seconds-based alias for `--dynamic-interval`, defaulting to 1 second when no value is provided. (#2478, Watch) +* Added InitSystem detection on Windows, reporting the first userland process (`smss.exe`). (InitSystem, Windows) +* Added CPU and GPU temperature detection support for Apple M5 series chips. (CPU / GPU, macOS) +* Reported `Basic` as the Windows theme when DWM is disabled. (Theme, Windows) +* Improved Ghostty terminal font detection performance. (#2122, TerminalFont) + * It now parses Ghostty directly instead of running `ghostty +show-config`. +* Added support for a wider range of TOML config syntaxes used by Alacritty. (#2456, TerminalFont) + +Bugfixes: +* Fixed a potential fish version detection error. (Shell, Linux) +* Fixed incorrect resolution reporting when a monitor advertises multiple preferred modes. (#2481, Display, Linux) +* Fixed connected monitors being missed when Wayland output events arrive late. (#2451, Display, Linux) +* Fixed VP9 codec detection on macOS. (Codec, macOS) +* Corrected Base64 encoding on big-endian hosts. (#2470) +* Fixed invalid URL parsing in the PublicIP module. (PublicIP) +* Correctly reported virtual GPUs on Windows. (#2461, GPU, Windows) +* Added a size limit for network responses to prevent excessive memory usage and mitigate potential attacks. (PublicIP / Weather) +* Fixed Ubuntu Studio Core detection (OS, Linux) +* Relaxed the HTTP response check so that both `HTTP/1.0` and `HTTP/1.1` responses are accepted when fetching data over the network. (PublicIP / Weather) +* Various internal cleanups and optimizations: + * Fixed multiple memory leaks (Separator, Camera, Codec, Display) + * Added integer overflow checks to the string buffer implementation + * Various code cleanups and compiler warning fixes + +Logos: +* Removed Hypros, MagpieOS, Furreto, EmperorOS and Magix + # 2.66.0 Changes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 56ef392e0b..4cd0df0d18 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.66.0 + VERSION 2.67.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" @@ -182,6 +182,29 @@ endif() set(WARNING_FLAGS "-Wall -Wextra -Wconversion -Werror=uninitialized -Werror=return-type -Werror=vla") +if(LINUX) + if(NOT IS_MUSL) # `NOT DEFINED IS_MUSL` doesn't work + execute_process( + COMMAND ${CMAKE_C_COMPILER} -print-file-name=libc.so + OUTPUT_VARIABLE LIBC_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(LIBC_PATH MATCHES "-musl") + set(IS_MUSL ON CACHE BOOL "Build with musl libc" FORCE) + endif() + endif() + + if(IS_MUSL) + message(STATUS "MUSL libc detected") + # Silence ioctl related warnings + set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-sign-conversion") + endif() +endif() + +# Minimal compiler version supported: GCC 13 (2023-04-26), Clang 16 (2023-03-17) +# Most C23 features are allowed with noticeable exceptions of `constexpr` and `#embed` +# See https://cppreference.com/c/compiler_support/23 for detail set(CMAKE_C_STANDARD 23) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion") @@ -411,6 +434,7 @@ set(LIBFASTFETCH_SRC src/common/impl/font.c src/common/impl/format.c src/common/impl/frequency.c + src/common/impl/genconfig.c src/common/impl/init.c src/common/impl/jsonconfig.c src/common/impl/library.c @@ -1100,7 +1124,7 @@ elseif(WIN32) src/detection/gpu/gpu_windows.cpp src/detection/host/host_windows.c src/detection/icons/icons_windows.c - src/detection/initsystem/initsystem_nosupport.c + src/detection/initsystem/initsystem_windows.c src/detection/keyboard/keyboard_windows.c src/detection/libc/libc_windows.cpp src/detection/lm/lm_nosupport.c diff --git a/README.md b/README.md index a55f2798dd..d2b0a53d92 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,8 @@ See [#1096](https://github.com/fastfetch-cli/fastfetch/issues/1096). ### Q: Fastfetch shows fewer dpkg packages than neofetch. Is it a bug? -Neofetch incorrectly counts `rc` packages (packages that have been removed but still have configuration files remaining). See bug: https://github.com/dylanaraps/neofetch/issues/2278 +1. Neofetch incorrectly counts `rc` packages for apt (packages that have been removed but still have configuration files remaining). See bug: https://github.com/dylanaraps/neofetch/issues/2278 +2. Neofetch incorrectly counts `gpg-pubkey` as packages for rpm. You may check the results of `dnf list --installed | wc -l` and `rpm -qa | wc -l` to see the difference. ### Q: I use Debian / Ubuntu / Debian-derived distro. My GPU is detected as `XXXX Device XXXX (VGA compatible)`. Is this a bug? diff --git a/doc/help.json b/doc/help.json index da6732fbc3..e24d294fc7 100644 --- a/doc/help.json +++ b/doc/help.json @@ -83,6 +83,16 @@ "type": "num", "default": 0 } + }, + { + "long": "watch", + "short": "w", + "desc": "Alias of dynamic-interval, but the value is in seconds instead of milliseconds. If no value is provided, it defaults to 1 second", + "arg": { + "type": "num", + "optional": true, + "default": 1 + } } ], "Config": [ @@ -97,26 +107,8 @@ }, { "long": "gen-config", - "desc": "Generate a minimal config file at the specified path", - "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if is \"-\"", - "arg": { - "type": "path", - "optional": true - } - }, - { - "long": "gen-config-full", - "desc": "Generate a full config file with all optional settings at the specified path", - "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if is \"-\"", - "arg": { - "type": "path", - "optional": true - } - }, - { - "long": "gen-config-force", - "desc": "Generate a config file at the specified path, overwriting any existing file", - "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\"", + "desc": "Interactively generate a config file at the specified path", + "remark": "Opens an interactive configuration UI when run in a terminal. Falls back to non-interactive generation when env-var `$NO_COLOR` is set or stdin/stdout is not a TTY. Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if is \"-\"", "arg": { "type": "path", "optional": true diff --git a/doc/json_schema.json b/doc/json_schema.json index e9271b2ff7..4197c241bf 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -912,11 +912,6 @@ "description": "Set the timeout (ms) when waiting for child processes, `-1` for no timeout", "default": 5000 }, - "preRun": { - "type": "string", - "description": "Command to run before printing logos", - "default": "" - }, "detectVersion": { "type": "boolean", "description": "Whether to detect and display component versions. Mainly for benchmarking", diff --git a/src/common/FFlist.h b/src/common/FFlist.h index cf1377b273..630b0734b2 100644 --- a/src/common/FFlist.h +++ b/src/common/FFlist.h @@ -4,6 +4,7 @@ #include #include #include +#include #define FF_LIST_DEFAULT_ALLOC 16 @@ -110,6 +111,19 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { return ffListGet(list, elementSize, list->length - 1); } +static inline void ffListRemoveAt(FFlist* list, uint32_t elementSize, uint32_t index) { + assert(list->length > index); + memmove(list->data + (index * elementSize), list->data + ((index + 1) * elementSize), (size_t) (list->length - index - 1) * elementSize); + --list->length; +} + +static inline void ffListInsertAt(FFlist* list, uint32_t elementSize, uint32_t index, const void* element) { + assert(list->length >= index); + ffListAdd(list, elementSize); + memmove(list->data + ((index + 1) * elementSize), list->data + (index * elementSize), (size_t) (list->length - index - 1) * elementSize); + memcpy(list->data + (index * elementSize), element, elementSize); +} + #define FF_LIST_FOR_EACH(itemType, itemVarName, listVar) \ for (itemType* itemVarName = (itemType*) (listVar).data; \ itemVarName - (itemType*) (listVar).data < (intptr_t) (listVar).length; \ @@ -125,6 +139,12 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { #define FF_LIST_ADD(itemType, listVar) (itemType*) ffListAdd(&(listVar), (uint32_t) sizeof(itemType)) +#define FF_LIST_REMOVE_AT(itemType, listVar, index) \ + ffListRemoveAt(&(listVar), (uint32_t) sizeof(itemType), (index)) + +#define FF_LIST_INSERT_AT(itemType, listVar, index, pElement) \ + ffListInsertAt(&(listVar), (uint32_t) sizeof(itemType), (index), (pElement)) + #define FF_LIST_FIRST(itemType, listVar) FF_LIST_GET(itemType, listVar, 0) #define FF_LIST_LAST(itemType, listVar) \ ({ \ @@ -134,9 +154,9 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { #define FF_LIST_CONTAINS(listVar, pCompElement, compFunc) \ ({ \ - typedef typeof(*(pCompElement)) compElementType; \ + typedef typeof(*(pCompElement)) compElementType; \ typedef bool compFuncType(const compElementType*, const compElementType*); \ - static_assert(__builtin_types_compatible_p(typeof(compFunc), compFuncType), "Incompatible callback function"); \ + static_assert(__builtin_types_compatible_p(typeof(compFunc), compFuncType), "Incompatible callback function"); \ ffListContains(&(listVar), (uint32_t) sizeof(*(pCompElement)), (pCompElement), (bool (*)(const void*, const void*)) compFunc); \ }) diff --git a/src/common/FFstrbuf.h b/src/common/FFstrbuf.h index 7b40bb42d1..e3722ad82d 100644 --- a/src/common/FFstrbuf.h +++ b/src/common/FFstrbuf.h @@ -221,9 +221,7 @@ static inline void ffStrbufClear(FFstrbuf* strbuf) { } static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) { - if (__builtin_expect(ffStrbufGetFree(strbuf) == 0, false)) { - ffStrbufEnsureFreeNoCheck(strbuf, 1); - } + ffStrbufEnsureFree(strbuf, 1); strbuf->chars[strbuf->length++] = c; strbuf->chars[strbuf->length] = '\0'; } @@ -232,9 +230,7 @@ static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { if (__builtin_expect(num == 0, false)) { return; } - if (__builtin_expect(ffStrbufGetFree(strbuf) < num, false)) { - ffStrbufEnsureFreeNoCheck(strbuf, num); - } + ffStrbufEnsureFree(strbuf, num); memset(&strbuf->chars[strbuf->length], c, num); strbuf->length += num; @@ -245,9 +241,7 @@ static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const cha if (__builtin_expect(value == nullptr || length == 0, false)) { return; } - if (__builtin_expect(ffStrbufGetFree(strbuf) < length, false)) { - ffStrbufEnsureFreeNoCheck(strbuf, length); - } + ffStrbufEnsureFree(strbuf, length); memcpy(&strbuf->chars[strbuf->length], value, length); strbuf->length += length; diff --git a/src/common/apple/smc_temps.c b/src/common/apple/smc_temps.c index 74b7929252..9fb18210f4 100644 --- a/src/common/apple/smc_temps.c +++ b/src/common/apple/smc_temps.c @@ -392,6 +392,27 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) { count += detectTemp(conn, "Tp0e", result); // CPU performance core 8 break; + case FF_TEMP_CPU_M5X: + count += detectTemp(conn, "Tp00", result); // CPU super core 1 + count += detectTemp(conn, "Tp04", result); // CPU super core 2 + count += detectTemp(conn, "Tp08", result); // CPU super core 3 + count += detectTemp(conn, "Tp0C", result); // CPU super core 4 + count += detectTemp(conn, "Tp0G", result); // CPU super core 5 + count += detectTemp(conn, "Tp0K", result); // CPU super core 6 + count += detectTemp(conn, "Tp0O", result); // CPU performance core 1 + count += detectTemp(conn, "Tp0R", result); // CPU performance core 2 + count += detectTemp(conn, "Tp0U", result); // CPU performance core 3 + count += detectTemp(conn, "Tp0X", result); // CPU performance core 4 + count += detectTemp(conn, "Tp0a", result); // CPU performance core 5 + count += detectTemp(conn, "Tp0d", result); // CPU performance core 6 + count += detectTemp(conn, "Tp0g", result); // CPU performance core 7 + count += detectTemp(conn, "Tp0j", result); // CPU performance core 8 + count += detectTemp(conn, "Tp0m", result); // CPU performance core 9 + count += detectTemp(conn, "Tp0p", result); // CPU performance core 10 + count += detectTemp(conn, "Tp0u", result); // CPU performance core 11 + count += detectTemp(conn, "Tp0y", result); // CPU performance core 12 + break; + case FF_TEMP_GPU_INTEL: count += detectTemp(conn, "TCGC", result); // GPU Intel Graphics goto gpu_unknown; @@ -442,6 +463,17 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) { count += detectTemp(conn, "Tg0k", result); // GPU 8 break; + case FF_TEMP_GPU_M5X: + count += detectTemp(conn, "Tg0U", result); // GPU 1 + count += detectTemp(conn, "Tg0X", result); // GPU 2 + count += detectTemp(conn, "Tg0d", result); // GPU 3 + count += detectTemp(conn, "Tg0g", result); // GPU 4 + count += detectTemp(conn, "Tg0j", result); // GPU 5 + count += detectTemp(conn, "Tg1Y", result); // GPU 6 + count += detectTemp(conn, "Tg1c", result); // GPU 7 + count += detectTemp(conn, "Tg1g", result); // GPU 8 + break; + case FF_TEMP_BATTERY: count += detectTemp(conn, "TB1T", result); // Battery count += detectTemp(conn, "TB2T", result); // Battery @@ -452,6 +484,9 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) { count += detectTemp(conn, "Tm06", result); // Memory 2 count += detectTemp(conn, "Tm08", result); // Memory 3 count += detectTemp(conn, "Tm09", result); // Memory 4 + count += detectTemp(conn, "Tm0p", result); // Memory Proximity 1 (M4) + count += detectTemp(conn, "Tm1p", result); // Memory Proximity 2 (M4) + count += detectTemp(conn, "Tm2p", result); // Memory Proximity 3 (M4) break; } diff --git a/src/common/apple/smc_temps.h b/src/common/apple/smc_temps.h index cf0c043055..0f03a912b3 100644 --- a/src/common/apple/smc_temps.h +++ b/src/common/apple/smc_temps.h @@ -14,6 +14,7 @@ enum FFTempType: uint8_t { FF_TEMP_CPU_M2X, FF_TEMP_CPU_M3X, FF_TEMP_CPU_M4X, + FF_TEMP_CPU_M5X, FF_TEMP_GPU_INTEL, FF_TEMP_GPU_AMD, @@ -22,6 +23,7 @@ enum FFTempType: uint8_t { FF_TEMP_GPU_M2X, FF_TEMP_GPU_M3X, FF_TEMP_GPU_M4X, + FF_TEMP_GPU_M5X, FF_TEMP_BATTERY, diff --git a/src/common/apple/version.m b/src/common/apple/version.m index 133ec5b196..e6d4fae621 100644 --- a/src/common/apple/version.m +++ b/src/common/apple/version.m @@ -16,7 +16,7 @@ bool ffGetAppNameAndVersion(const char* exePath, FFstrbuf* retName, FFstrbuf* re lastSlash -= strlen("MacOS"); char infoPlistPath[PATH_MAX]; - memcpy(infoPlistPath, exePath, lastSlash - exePath); + memcpy(infoPlistPath, exePath, (size_t) (lastSlash - exePath)); memcpy(infoPlistPath + (lastSlash - exePath), "Info.plist", sizeof("Info.plist")); // X.app/Contents/Info.plist NSError* error; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@(infoPlistPath)] diff --git a/src/common/ffdata.h b/src/common/ffdata.h index 76857a1ce3..fe2d4f66f2 100644 --- a/src/common/ffdata.h +++ b/src/common/ffdata.h @@ -19,4 +19,5 @@ typedef struct FFdata { FFstrbuf genConfigPath; // Path to generate configuration file FFDataResultDocType docType; // Type of result document bool configLoaded; + bool genConfigInteractive; // `--gen-config` entered the interactive CUI } FFdata; diff --git a/src/common/genconfig.h b/src/common/genconfig.h new file mode 100644 index 0000000000..91f136a3d0 --- /dev/null +++ b/src/common/genconfig.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common/ffdata.h" + +// Runs the interactive config generation CUI. +// +// On success (the user saved the config), it sets `data->structure` to the user chosen +// module structure and `instance.config.logo.type` to the user chosen logo type, creates +// the result document and returns true. The caller is expected to call `writeConfigFile`. +// Returns false if the user quits without saving. +bool ffGenConfigInteractive(FFdata* data); diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c index 35747f8fe4..2ed3e9de84 100644 --- a/src/common/impl/FFstrbuf.c +++ b/src/common/impl/FFstrbuf.c @@ -1,6 +1,7 @@ #include "common/FFstrbuf.h" #include "common/mallocHelper.h" #include "common/strutil.h" +#include "common/debug.h" #include #include @@ -64,13 +65,24 @@ FFstrbuf ffStrbufCreateF(const char* format, ...) { } void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) { - uint32_t allocate = strbuf->length + free; + uint32_t allocate; + if (__builtin_expect(__builtin_uadd_overflow(strbuf->length, free, &allocate), false)) { + FF_DEBUG("Error: Integer overflow when calculating allocation size. Aborting"); + abort(); + } + if (allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC) { // `<` for null terminator allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC; } else { + if (__builtin_expect(allocate > (UINT32_MAX >> 1), false)) { + // User tried to allocate more than 2GB of memory, which exceeds the maximum size supported by FFstrbuf. + // This is likely an error or an attempt to exploit the program. Abort to prevent potential issues. + FF_DEBUG("Error: Attempted to allocate %" PRIu32 " bytes more than 2GB of memory in FFstrbuf. Aborting", allocate); + abort(); + } + // Round up to the next power of 2. // If the value is already a power of 2, it will be rounded up to the next power of 2. - assert(allocate < (UINT32_MAX >> 1)); allocate = 1U << (32 - __builtin_clz(allocate)); } @@ -91,16 +103,17 @@ void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) { // Ensure that at least `free` bytes are available in the buffer besides the current length // for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL) +// This function ensures a dynamic buffer is allocated even if free == 0 void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) { - uint32_t oldFree = ffStrbufGetFree(strbuf); - if (oldFree >= free && !(strbuf->allocated == 0 && strbuf->length > 0)) { - return; - } - - uint32_t newCap = strbuf->allocated + (free - oldFree); + uint32_t newCap; if (strbuf->allocated == 0) { - newCap += strbuf->length + 1; + assert(strbuf->length < UINT32_MAX - 1); // We don't use static strings with length >= UINT32_MAX - 1, so this should never happen + if (__builtin_expect(__builtin_uadd_overflow(strbuf->length + 1, free, &newCap), false)) { + FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting"); + abort(); + } + char* newbuf = malloc(sizeof(*strbuf->chars) * newCap); if (strbuf->length == 0) { *newbuf = '\0'; @@ -109,6 +122,15 @@ void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) { } strbuf->chars = newbuf; } else { + uint32_t oldFree = ffStrbufGetFree(strbuf); + if (oldFree >= free) { + return; + } + + if (__builtin_expect(__builtin_uadd_overflow(strbuf->allocated, free - oldFree, &newCap), false)) { + FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting"); + abort(); + } strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap); } @@ -410,9 +432,7 @@ bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) { if (strbuf->allocated == 0) { // static string - if (index < strbuf->length) { - ffStrbufInitNS(strbuf, index, strbuf->chars); - } + ffStrbufInitNS(strbuf, index, strbuf->chars); return true; } diff --git a/src/common/impl/base64.c b/src/common/impl/base64.c index 98a415ffbd..fa987c303c 100644 --- a/src/common/impl/base64.c +++ b/src/common/impl/base64.c @@ -6,7 +6,13 @@ void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* char* out = output; const char* ends = str + (size - size % 3); while (str != ends) { - uint32_t n = __builtin_bswap32(*(uint32_t*) str); + uint32_t n = *(uint32_t*) str; + #if !__BIG_ENDIAN__ + // The 3 input bytes must be laid out big-endian (str[0] in the most + // significant position). On little-endian hosts swap; on big-endian + // hosts the word is already in the right order. + n = __builtin_bswap32(n); + #endif *out++ = chars[(n >> 26) & 63]; *out++ = chars[(n >> 20) & 63]; *out++ = chars[(n >> 14) & 63]; diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c index eacf4eab9b..c4225a711b 100644 --- a/src/common/impl/commandoption.c +++ b/src/common/impl/commandoption.c @@ -175,7 +175,7 @@ static bool parseStructureCommand( for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(line[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrEqualsIgnCase(line, baseInfo->name)) { - uint8_t optionBuf[FF_OPTION_MAX_SIZE]; + alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); if (data->resultDoc != nullptr) { fn(data, baseInfo, optionBuf); @@ -244,11 +244,6 @@ void ffPrintCommandOption(FFdata* data) { } void ffMigrateCommandOptionToJsonc(FFdata* data) { - // If we don't have a custom structure, use the default one - if (data->structure.length == 0) { - ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string - } - char* moduleType = nullptr; size_t moduleLen = 0; while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) { diff --git a/src/common/impl/format.c b/src/common/impl/format.c index 978011bd1e..d70f1faa58 100644 --- a/src/common/impl/format.c +++ b/src/common/impl/format.c @@ -109,7 +109,21 @@ static inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, } static inline bool formatArgSet(const FFformatarg* arg) { - return arg->value != nullptr && ((arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0)); + // clang-format off + return arg->value != nullptr && ( + (arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || + (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || + (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || + (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || + (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || + (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0) + ); + // clang-format on } [[maybe_unused]] static inline void normalizeArgName(FFstrbuf* dst, const char* src) { diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c new file mode 100644 index 0000000000..e60d8b5f1e --- /dev/null +++ b/src/common/impl/genconfig.c @@ -0,0 +1,1063 @@ +#include "common/genconfig.h" + +#include "fastfetch.h" + +#include "common/io.h" +#include "common/strutil.h" +#include "detection/terminalsize/terminalsize.h" +#include "modules/modules.h" + +#include +#include +#include + +#ifndef _WIN32 + #include + #include + #include + #include +#else + #include +#endif + +// Layout (rows): +// 0: title +// 1: blank +// 2: logo type row +// 3: output mode row +// 4: blank +// 5: modules title row +// 6: blank +// 7..7+listRows-1: module grid +// 7+listRows: description row +// 8+listRows: help line 1 +// 9+listRows: help line 2 +#define FF_GEN_CONFIG_LIST_TOP 7 +#define FF_GEN_CONFIG_BOTTOM_CHROME 3 + +// Poll wait timeout (ms) while idle. A longer wait avoids frequent full-screen +// redraws (flicker); terminal size changes redraw immediately via SIGWINCH / +// console window-size events. +#define FF_GEN_CONFIG_POLL_TIMEOUT 1000 + +typedef enum : uint8_t { + FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED = false, + FF_GEN_CONFIG_ITEM_STATUS_SELECTED = true, + FF_GEN_CONFIG_ITEM_STATUS_SPECIAL = 2, +} FFGenConfigItemStatus; + +typedef struct FFGenConfigItem { + FFModuleBaseInfo* baseInfo; + FFGenConfigItemStatus status; +} FFGenConfigItem; + +typedef struct FFGenConfigLayout { + uint16_t listLeft; + uint16_t listRows; + uint32_t colWidth; + uint32_t columns; + uint32_t itemsPerColumn; +} FFGenConfigLayout; + +typedef struct FFGenConfigUI { + FFlist items; // FFGenConfigItem + uint32_t cursor; + uint32_t viewOffset; + FFLogoType logoType; + bool fullConfig; + bool confirmingOverwrite; + uint16_t rows; + uint16_t cols; + FFGenConfigLayout layout; +} FFGenConfigUI; + +typedef enum : uint8_t { + FF_GEN_KEY_UP, + FF_GEN_KEY_DOWN, + FF_GEN_KEY_LEFT, + FF_GEN_KEY_RIGHT, + FF_GEN_KEY_ENTER, + FF_GEN_KEY_ESCAPE, + FF_GEN_KEY_CHAR, + FF_GEN_KEY_RESIZE, + FF_GEN_KEY_UNKNOWN, +} FFGenConfigKey; + +#ifndef _WIN32 +static struct termios gOriginalTermios; +static bool gRawModeActive = false; +static volatile sig_atomic_t gWindowResized = 0; + +static void onWindowResize(int sig) { + (void) sig; + gWindowResized = 1; +} + +static void restoreRawMode(void) { + if (gRawModeActive) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &gOriginalTermios); + gRawModeActive = false; + } +} + +static void enterRawMode(void) { + if (gRawModeActive) { + return; + } + if (tcgetattr(STDIN_FILENO, &gOriginalTermios) != 0) { + return; + } + struct termios raw = gOriginalTermios; + raw.c_lflag &= (tcflag_t) ~(ICANON | ECHO | ISIG | IEXTEN); + raw.c_iflag &= (tcflag_t) ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP); + raw.c_cflag |= CS8; + raw.c_oflag &= (tcflag_t) ~OPOST; + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) { + return; + } + gRawModeActive = true; + + struct sigaction sa = { .sa_handler = onWindowResize }; + sigemptyset(&sa.sa_mask); + sigaction(SIGWINCH, &sa, nullptr); +} +#else +static DWORD gOriginalInputMode; +static DWORD gOriginalOutputMode; +static bool gRawModeActive = false; + +static void restoreRawMode(void) { + if (gRawModeActive) { + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), gOriginalInputMode); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), gOriginalOutputMode); + gRawModeActive = false; + } +} + +static void enterRawMode(void) { + if (gRawModeActive) { + return; + } + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + + if (GetConsoleMode(hInput, &gOriginalInputMode)) { + DWORD newMode = gOriginalInputMode; + newMode &= ~(DWORD) (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); + newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_WINDOW_INPUT; + SetConsoleMode(hInput, newMode); + } + + if (GetConsoleMode(hOutput, &gOriginalOutputMode)) { + DWORD newMode = gOriginalOutputMode; + newMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + SetConsoleMode(hOutput, newMode); + } + + FlushConsoleInputBuffer(hInput); + gRawModeActive = true; +} +#endif + +static void getTerminalSize(uint16_t* rows, uint16_t* cols) { + FFTerminalSizeResult size; + if (ffDetectTerminalSize(&size) && size.rows > 0 && size.columns > 0) { + *rows = size.rows; + *cols = size.columns; + } else { + *rows = 24; + *cols = 80; + } +} + +static int compareModuleInfo(const void* a, const void* b) { + const FFModuleBaseInfo* const* ma = (const FFModuleBaseInfo* const*) a; + const FFModuleBaseInfo* const* mb = (const FFModuleBaseInfo* const*) b; + if ((*ma)->defaultOrder != (*mb)->defaultOrder) { + return (int) (*ma)->defaultOrder - (int) (*mb)->defaultOrder; + } + return strcmp((*ma)->name, (*mb)->name); +} + +static void collectModuleInfos(FFlist* modules) { + ffListInitA(modules, sizeof(FFModuleBaseInfo*), 64); + for (uint32_t i = 0; i <= 'Z' - 'A'; ++i) { + for (FFModuleBaseInfo** it = ffModuleInfos[i]; *it; ++it) { + if ((*it)->defaultOrder == 0) { + continue; + } + *(FFModuleBaseInfo**) ffListAdd(modules, sizeof(FFModuleBaseInfo*)) = *it; + } + } + ffListSort(modules, sizeof(FFModuleBaseInfo*), compareModuleInfo); +} + +static void initItems(FFlist* items, const FFlist* modules, FFdata* data) { + ffListInitA(items, sizeof(FFGenConfigItem), modules->length + 8); + FF_LIST_FOR_EACH (FFModuleBaseInfo*, info, *modules) { + FFGenConfigItem* item = FF_LIST_ADD(FFGenConfigItem, *items); + item->baseInfo = *info; + if (*info == &ffBreakModuleInfo || *info == &ffSeparatorModuleInfo) { + item->status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL; + } else { + item->status = ffStrbufSeparatedContainIgnCaseS(&data->structure, (*info)->name, ':') && + !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, (*info)->name, ':') + ? FF_GEN_CONFIG_ITEM_STATUS_SELECTED + : FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED; + } + } +} + +static void moveItemDown(FFGenConfigUI* ui, uint32_t idx) { + if (idx + 1 >= ui->items.length) { + return; + } + FFGenConfigItem* items = (FFGenConfigItem*) ui->items.data; + FFGenConfigItem tmp = items[idx]; + items[idx] = items[idx + 1]; + items[idx + 1] = tmp; + ui->cursor = idx + 1; +} + +static void moveItemUp(FFGenConfigUI* ui, uint32_t idx) { + if (idx == 0) { + return; + } + FFGenConfigItem* items = (FFGenConfigItem*) ui->items.data; + FFGenConfigItem tmp = items[idx]; + items[idx] = items[idx - 1]; + items[idx - 1] = tmp; + ui->cursor = idx - 1; +} + +static void removeAllBreaksSeparators(FFGenConfigUI* ui) { + for (uint32_t i = ui->items.length; i-- > 0;) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, i); + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, i); + } + } + if (ui->cursor >= ui->items.length && ui->items.length > 0) { + ui->cursor = ui->items.length - 1; + } +} + +static void computeLayout(FFGenConfigUI* ui) { + FFGenConfigLayout* layout = &ui->layout; + if (ui->rows < 10) { + ui->rows = 10; + } + int32_t listRows = (int32_t) ui->rows - FF_GEN_CONFIG_LIST_TOP - FF_GEN_CONFIG_BOTTOM_CHROME; + layout->listRows = listRows < 0 ? 0 : (uint16_t) listRows; + layout->listLeft = 2; + + uint32_t maxContentWidth = 0; + FF_LIST_FOR_EACH (FFGenConfigItem, item, ui->items) { + uint32_t w = (uint32_t) strlen(item->baseInfo->name) + 4; // "[x] " / "[─] " prefix + if (w > maxContentWidth) { + maxContentWidth = w; + } + } + layout->colWidth = maxContentWidth + 2; + if (layout->colWidth < 1) { + layout->colWidth = 1; + } + + uint32_t gridWidth = ui->cols > 4 ? (uint32_t) ui->cols - 4 : 1; + uint32_t maxColumns = gridWidth / layout->colWidth; + if (maxColumns < 1) { + maxColumns = 1; + } + + uint32_t length = ui->items.length; + if (length == 0 || layout->listRows == 0) { + layout->columns = 1; + layout->itemsPerColumn = length > 0 ? length : 1; + return; + } + uint32_t neededColumns = (length + layout->listRows - 1) / layout->listRows; + if (neededColumns < 1) { + neededColumns = 1; + } + layout->columns = neededColumns < maxColumns ? neededColumns : maxColumns; + layout->itemsPerColumn = (length + layout->columns - 1) / layout->columns; +} + +static void recomputeView(FFGenConfigUI* ui) { + const FFGenConfigLayout* layout = &ui->layout; + uint32_t length = ui->items.length; + if (length == 0) { + ui->cursor = 0; + ui->viewOffset = 0; + return; + } + if (ui->cursor >= length) { + ui->cursor = length - 1; + } + + uint32_t rowsPerColumn = layout->itemsPerColumn; + if (rowsPerColumn <= layout->listRows) { + ui->viewOffset = 0; + return; + } + + uint32_t cursorRow = ui->cursor % rowsPerColumn; + uint32_t pageTop = ui->viewOffset; + if (cursorRow < pageTop) { + pageTop = cursorRow; + } else if (cursorRow >= pageTop + layout->listRows) { + pageTop = cursorRow - layout->listRows + 1; + } + uint32_t maxTop = rowsPerColumn - layout->listRows; + if (pageTop > maxTop) { + pageTop = maxTop; + } + ui->viewOffset = pageTop; +} + +static void toggleItem(FFGenConfigUI* ui, uint32_t idx) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, idx); + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + return; + } + item->status = !item->status; +} + +static void selectAllModules(FFGenConfigUI* ui) { + FF_LIST_FOR_EACH (FFGenConfigItem, item, ui->items) { + if (item->status != FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + item->status = FF_GEN_CONFIG_ITEM_STATUS_SELECTED; + } + } +} + +static void invertAllModules(FFGenConfigUI* ui) { + FF_LIST_FOR_EACH (FFGenConfigItem, item, ui->items) { + if (item->status != FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + item->status = !item->status; + } + } +} + +static void addBreakBelow(FFGenConfigUI* ui, uint32_t idx) { + FFGenConfigItem br = { .baseInfo = &ffBreakModuleInfo, .status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL }; + FF_LIST_INSERT_AT(FFGenConfigItem, ui->items, idx + 1, &br); +} + +static void addSeparatorBelow(FFGenConfigUI* ui, uint32_t idx) { + FFGenConfigItem sep = { .baseInfo = &ffSeparatorModuleInfo, .status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL }; + FF_LIST_INSERT_AT(FFGenConfigItem, ui->items, idx + 1, &sep); +} + +static void cycleLogoType(FFGenConfigUI* ui, int delta) { + switch (ui->logoType) { + case FF_LOGO_TYPE_AUTO: + ui->logoType = delta > 0 ? FF_LOGO_TYPE_SMALL : FF_LOGO_TYPE_NONE; + break; + case FF_LOGO_TYPE_SMALL: + ui->logoType = delta > 0 ? FF_LOGO_TYPE_NONE : FF_LOGO_TYPE_AUTO; + break; + default: + ui->logoType = delta > 0 ? FF_LOGO_TYPE_AUTO : FF_LOGO_TYPE_SMALL; + break; + } +} + +static uint32_t countSelectedModules(const FFlist* items) { + uint32_t count = 0; + FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SELECTED) { + ++count; + } + } + return count; +} + +static uint32_t countModules(const FFlist* items) { + uint32_t count = 0; + FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { + if (item->status != FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + ++count; + } + } + return count; +} + +// ---- Row builder ----------------------------------------------------------- + +typedef struct FFRow { + FFstrbuf buf; + uint32_t visualCol; + uint16_t cols; +} FFRow; + +static void rowInit(FFRow* row, uint16_t cols) { + ffStrbufInitA(&row->buf, (uint32_t) cols + 16); + row->visualCol = 0; + row->cols = cols; +} + +static void rowAppendRaw(FFRow* row, const char* s) { + ffStrbufAppendS(&row->buf, s); +} + +static void rowAppendVisual(FFRow* row, const char* s) { + uint32_t len = (uint32_t) strlen(s); + ffStrbufAppendNS(&row->buf, len, s); + row->visualCol += ffUtf8StrWidth(s, len); +} + +static void rowAppendVisualTruncated(FFRow* row, const char* s, uint32_t maxWidth) { + uint32_t total = (uint32_t) strlen(s); + uint32_t bytes = total; + if (ffUtf8StrWidth(s, total) > maxWidth) { + bytes = 0; + uint32_t used = 0; + const char* p = s; + uint32_t remaining = total; + while (remaining > 0 && used < maxWidth) { + uint8_t w = 0; + uint8_t cbytes = ffUtf8CharLenWidth(p, remaining, &w); + if (cbytes == 0 || used + w > maxWidth) { + break; + } + used += w; + bytes += cbytes; + p += cbytes; + remaining -= cbytes; + } + } + ffStrbufAppendNS(&row->buf, bytes, s); + row->visualCol += ffUtf8StrWidth(s, bytes); +} + +static void rowPadVisual(FFRow* row, uint32_t col) { + while (row->visualCol < col) { + ffStrbufAppendC(&row->buf, ' '); + ++row->visualCol; + } +} + +static uint32_t rowAnsiSeqLength(const char* s, uint32_t length) { + uint32_t i = 1; + while (i < length) { + uint8_t c = (uint8_t) s[i]; + if (c >= 0x40 && c <= 0x7e) { + return i + 1; + } + ++i; + } + return length; +} + +static void truncateRow(FFRow* row) { + FFstrbuf src = row->buf; + ffStrbufInitA(&row->buf, row->cols + 32); + uint32_t used = 0; + uint32_t remaining = src.length; + const char* p = src.chars; + while (remaining > 0 && used < row->cols) { + if (*p == '\x1b') { + uint32_t len = rowAnsiSeqLength(p, remaining); + ffStrbufAppendNS(&row->buf, len, p); + p += len; + remaining -= len; + } else { + uint8_t w = 0; + uint8_t cbytes = ffUtf8CharLenWidth(p, remaining, &w); + if (cbytes == 0) { + cbytes = 1; + w = 1; + } + if (used + w > row->cols) { + break; + } + ffStrbufAppendNS(&row->buf, cbytes, p); + used += w; + p += cbytes; + remaining -= cbytes; + } + } + ffStrbufDestroy(&src); + row->visualCol = used; +} + +static void finishRow(FFRow* row, FFstrbuf* out, bool isLastRow) { + if (row->visualCol > row->cols) { + truncateRow(row); + ffStrbufAppendS(&row->buf, "\e[m"); + } + if (row->visualCol < row->cols) { + rowPadVisual(row, row->cols); + } + ffStrbufAppend(out, &row->buf); + ffStrbufAppendS(out, "\e[K"); + if (!isLastRow) { + ffStrbufAppendS(out, "\r\n"); + } + ffStrbufDestroy(&row->buf); +} + +static void drawModuleItem(FFRow* row, const FFGenConfigItem* item, uint32_t cellWidth, bool highlight) { + const uint32_t nameMax = cellWidth > 4 ? cellWidth - 4 : 0; + + if (item->baseInfo == &ffBreakModuleInfo) { + rowAppendRaw(row, highlight ? "\e[1;95;7m" : "\e[1;95m"); + ffStrbufAppendS(&row->buf, "[─]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendRaw(row, highlight ? "\e[1;95;7m" : "\e[1;95m"); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + return; + } + if (item->baseInfo == &ffSeparatorModuleInfo) { + rowAppendRaw(row, highlight ? "\e[1;96;7m" : "\e[1;96m"); + ffStrbufAppendS(&row->buf, "[═]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendRaw(row, highlight ? "\e[1;96;7m" : "\e[1;96m"); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + return; + } + + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SELECTED) { + rowAppendRaw(row, highlight ? "\e[1;92;7m" : "\e[1;92m"); + ffStrbufAppendS(&row->buf, "[x]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendRaw(row, highlight ? "\e[1;97;7m" : "\e[1;97m"); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + } else { + rowAppendRaw(row, highlight ? "\e[90;7m" : "\e[90m"); + ffStrbufAppendS(&row->buf, "[ ]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + } +} + +static void drawItemCell(FFRow* row, uint32_t startCol, uint32_t cellWidth, const FFGenConfigItem* item, bool highlight) { + rowPadVisual(row, startCol); + drawModuleItem(row, item, cellWidth, highlight); + rowPadVisual(row, startCol + cellWidth); +} + +static void drawLogoOption(FFRow* row, const char* name, bool active) { + if (active) { + rowAppendRaw(row, "\e[1;92m"); + rowAppendVisual(row, "● "); + rowAppendRaw(row, "\e[m"); + rowAppendRaw(row, "\e[1m"); + rowAppendVisual(row, name); + rowAppendRaw(row, "\e[m"); + } else { + rowAppendRaw(row, "\e[90m"); + rowAppendVisual(row, "○ "); + rowAppendVisual(row, name); + rowAppendRaw(row, "\e[m"); + } +} + +static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { + getTerminalSize(&ui->rows, &ui->cols); + computeLayout(ui); + recomputeView(ui); + + const FFGenConfigLayout* layout = &ui->layout; + const uint16_t cols = ui->cols; + const uint32_t selectedCount = countSelectedModules(&ui->items); + const uint32_t moduleCount = countModules(&ui->items); + + ffStrbufAppendS(out, "\e[?25l\e[H"); + uint32_t rowCount = 0; + + FFRow row; + + // Row 0: title + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;36m"); + rowAppendVisual(&row, "fastfetch"); + rowAppendRaw(&row, "\e[m"); + rowAppendRaw(&row, "\e[1m"); + rowAppendVisual(&row, " configuration"); + rowAppendRaw(&row, "\e[m"); + if (cols >= 48) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, "interactive config generator"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 1: blank + rowInit(&row, cols); + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 2: logo type + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;97m"); + rowAppendVisual(&row, "Logo type:"); + rowAppendRaw(&row, "\e[m"); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "default", ui->logoType == FF_LOGO_TYPE_AUTO); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "small", ui->logoType == FF_LOGO_TYPE_SMALL); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "none", ui->logoType == FF_LOGO_TYPE_NONE); + if (cols >= 72) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, "(l)"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 3: output mode + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;97m"); + rowAppendVisual(&row, "Output:"); + rowAppendRaw(&row, "\e[m"); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "minimal", !ui->fullConfig); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "full", ui->fullConfig); + if (cols >= 64) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, "(o)"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 4: blank + rowInit(&row, cols); + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 5: modules title + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;97m"); + rowAppendVisual(&row, "Modules:"); + rowAppendRaw(&row, "\e[m"); + rowAppendRaw(&row, "\e[92m"); + FF_STRBUF_AUTO_DESTROY counter = ffStrbufCreateA(32); + ffStrbufAppendF(&counter, " [%u/%u selected]", selectedCount, moduleCount); + rowAppendVisual(&row, counter.chars); + rowAppendRaw(&row, "\e[m"); + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 6: blank + rowInit(&row, cols); + finishRow(&row, out, ++rowCount == ui->rows); + + // Grid rows + const uint32_t rowsPerColumn = layout->itemsPerColumn; + const uint32_t length = ui->items.length; + for (uint16_t screenRow = 0; screenRow < layout->listRows; ++screenRow) { + rowInit(&row, cols); + for (uint32_t c = 0; c < layout->columns; ++c) { + uint32_t startCol = layout->listLeft + c * layout->colWidth; + uint32_t index = (ui->viewOffset + screenRow) + c * rowsPerColumn; + if (screenRow < rowsPerColumn && index < length) { + const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, index); + drawItemCell(&row, startCol, layout->colWidth, item, ui->cursor == index); + } else { + rowPadVisual(&row, startCol + layout->colWidth); + } + } + finishRow(&row, out, ++rowCount == ui->rows); + } + + // Row after grid: description + rowInit(&row, cols); + if (ui->cursor < ui->items.length) { + const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor); + if (item->baseInfo && item->baseInfo->description) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisualTruncated(&row, item->baseInfo->description, cols > 4 ? cols - 4 : 1); + rowAppendRaw(&row, "\e[m"); + } + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Help lines + rowInit(&row, cols); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, " ↑/↓ k/j move ←/→ col Space toggle f/F all/invert K/J reorder b/B break/sep d/D del"); + if (ui->confirmingOverwrite) { + rowAppendRaw(&row, "\e[m"); + rowAppendRaw(&row, "\e[1;93m"); + rowAppendVisual(&row, " File exists. Overwrite? (y/N)"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + rowInit(&row, cols); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, " l/L logo o minimal/full s/Enter save q/Esc quit g/G top/bottom"); + rowAppendRaw(&row, "\e[m"); + finishRow(&row, out, ++rowCount == ui->rows); + + ffStrbufAppendS(out, "\e[J"); +} + +// ---- Key input ------------------------------------------------------------- + +static FFGenConfigKey readKey(char* outChar) { +#ifndef _WIN32 + if (gWindowResized) { + gWindowResized = 0; + return FF_GEN_KEY_RESIZE; + } + struct pollfd pfd = { + .fd = STDIN_FILENO, + .events = POLLIN, + }; + if (poll(&pfd, 1, FF_GEN_CONFIG_POLL_TIMEOUT) <= 0) { + if (gWindowResized) { + gWindowResized = 0; + return FF_GEN_KEY_RESIZE; + } + return FF_GEN_KEY_UNKNOWN; + } +#else + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + if (WaitForSingleObject(hInput, FF_GEN_CONFIG_POLL_TIMEOUT) != WAIT_OBJECT_0) { + return FF_GEN_KEY_UNKNOWN; + } + DWORD numEvents = 0; + if (GetNumberOfConsoleInputEvents(hInput, &numEvents) && numEvents > 0) { + bool consumed = false; + INPUT_RECORD record; + while (numEvents > 0) { + DWORD n = 0; + if (!PeekConsoleInputW(hInput, &record, 1, &n) || n == 0) { + break; + } + if (record.EventType == KEY_EVENT) { + break; // keep key events for ffReadFDData + } + if (!ReadConsoleInputW(hInput, &record, 1, &n) || n == 0) { + break; + } + consumed = true; + --numEvents; + } + if (consumed) { + return FF_GEN_KEY_RESIZE; + } + } +#endif + + char buf[16]; + ssize_t n = ffReadFDData(FFUnixFD2NativeFD(STDIN_FILENO), sizeof(buf), buf); + if (n <= 0) { + return FF_GEN_KEY_UNKNOWN; + } + if (buf[0] == '\r' || buf[0] == '\n') { + return FF_GEN_KEY_ENTER; + } + if (buf[0] != '\x1b') { + *outChar = buf[0]; + return FF_GEN_KEY_CHAR; + } + if (n == 1) { + return FF_GEN_KEY_ESCAPE; + } + + if (buf[1] == '[' || buf[1] == 'O') { + size_t i = 2; + if (i < (size_t) n && buf[i] >= 'A' && buf[i] <= 'D') { + switch (buf[i]) { + case 'A': + return FF_GEN_KEY_UP; + case 'B': + return FF_GEN_KEY_DOWN; + case 'C': + return FF_GEN_KEY_RIGHT; + case 'D': + return FF_GEN_KEY_LEFT; + } + } + uint32_t param = 0; + while (i < (size_t) n && buf[i] >= '0' && buf[i] <= '9') { + param = param * 10 + (uint32_t) (buf[i] - '0'); + ++i; + } + while (i < (size_t) n && buf[i] == ';') { + ++i; + while (i < (size_t) n && buf[i] >= '0' && buf[i] <= '9') { + ++i; + } + } + if (i < (size_t) n) { + switch (buf[i]) { + case 'A': + return FF_GEN_KEY_UP; + case 'B': + return FF_GEN_KEY_DOWN; + case 'C': + return FF_GEN_KEY_RIGHT; + case 'D': + return FF_GEN_KEY_LEFT; + } + } + return FF_GEN_KEY_UNKNOWN; + } + + return FF_GEN_KEY_ESCAPE; +} + +static void moveCursorLeft(FFGenConfigUI* ui) { + uint32_t rowsPerColumn = ui->layout.itemsPerColumn; + if (rowsPerColumn == 0) { + return; + } + if (ui->cursor >= rowsPerColumn) { + ui->cursor -= rowsPerColumn; + } +} + +static void moveCursorRight(FFGenConfigUI* ui) { + uint32_t rowsPerColumn = ui->layout.itemsPerColumn; + if (rowsPerColumn == 0) { + return; + } + uint32_t newCursor = ui->cursor + rowsPerColumn; + if (newCursor < ui->items.length) { + ui->cursor = newCursor; + } +} + +static void moveCursorUp(FFGenConfigUI* ui) { + if (ui->cursor > 0) { + --ui->cursor; + } else { + ui->cursor = ui->items.length > 0 ? ui->items.length - 1 : 0; + } +} + +static void moveCursorDown(FFGenConfigUI* ui) { + if (ui->items.length == 0) { + return; + } + if (ui->cursor + 1 < ui->items.length) { + ++ui->cursor; + } else { + ui->cursor = 0; + } +} + +// Returns 1 on save, 0 on quit, -1 on continue +static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileExists) { + if (key == FF_GEN_KEY_UNKNOWN) { + return -1; + } + if (key == FF_GEN_KEY_RESIZE) { + return -1; // Terminal resized: redraw with new size + } + + if (ui->confirmingOverwrite) { + if (key == FF_GEN_KEY_CHAR && (ch == 'y' || ch == 'Y')) { + return 1; + } + if ((key == FF_GEN_KEY_CHAR && (ch == 'n' || ch == 'N')) || key == FF_GEN_KEY_ESCAPE || key == FF_GEN_KEY_ENTER) { + ui->confirmingOverwrite = false; + } + return -1; + } + + switch (key) { + case FF_GEN_KEY_UP: + moveCursorUp(ui); + break; + case FF_GEN_KEY_DOWN: + moveCursorDown(ui); + break; + case FF_GEN_KEY_LEFT: + moveCursorLeft(ui); + break; + case FF_GEN_KEY_RIGHT: + moveCursorRight(ui); + break; + case FF_GEN_KEY_ENTER: + if (fileExists) { + ui->confirmingOverwrite = true; + } else { + return 1; + } + break; + case FF_GEN_KEY_ESCAPE: + return 0; + case FF_GEN_KEY_CHAR: + if (ch == 'q') { + return 0; + } else if (ch == 's') { + if (fileExists) { + ui->confirmingOverwrite = true; + } else { + return 1; + } + } else if (ch == ' ') { + if (ui->cursor < ui->items.length) { + toggleItem(ui, ui->cursor); + } + } else if (ch == 'f') { + selectAllModules(ui); + } else if (ch == 'F') { + invertAllModules(ui); + } else if (ch == 'o') { + ui->fullConfig = !ui->fullConfig; + } else if (ch == 'j') { + moveCursorDown(ui); + } else if (ch == 'k') { + moveCursorUp(ui); + } else if (ch == 'J') { + if (ui->cursor < ui->items.length) { + moveItemDown(ui, ui->cursor); + } + } else if (ch == 'K') { + if (ui->cursor < ui->items.length) { + moveItemUp(ui, ui->cursor); + } + } else if (ch == 'b') { + if (ui->cursor < ui->items.length) { + addBreakBelow(ui, ui->cursor); + } + } else if (ch == 'B') { + if (ui->cursor < ui->items.length) { + addSeparatorBelow(ui, ui->cursor); + } + } else if (ch == 'd') { + if (ui->cursor < ui->items.length) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor); + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, ui->cursor); + if (ui->cursor >= ui->items.length && ui->items.length > 0) { + ui->cursor = ui->items.length - 1; + } + } + } + } else if (ch == 'D') { + removeAllBreaksSeparators(ui); + } else if (ch == 'g') { + ui->cursor = 0; + } else if (ch == 'G') { + ui->cursor = ui->items.length > 0 ? ui->items.length - 1 : 0; + } else if (ch == 'l') { + cycleLogoType(ui, 1); + } else if (ch == 'L') { + cycleLogoType(ui, -1); + } else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') { + return 0; + } + break; + default: + break; + } + return -1; +} + +static int runCui(FFGenConfigUI* ui, bool fileExists) { + while (true) { + FF_STRBUF_AUTO_DESTROY frame = ffStrbufCreateA((uint32_t) ui->cols * ((uint32_t) ui->rows + 4)); + renderFrame(ui, &frame); + ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), frame.length, frame.chars); + fflush(stdout); + + char ch = 0; + FFGenConfigKey key = readKey(&ch); + int result = handleKey(ui, key, ch, fileExists); + if (result != -1) { + return result; + } + } +} + +static FFLogoType initialLogoType(void) { + switch (instance.config.logo.type) { + case FF_LOGO_TYPE_SMALL: + return FF_LOGO_TYPE_SMALL; + case FF_LOGO_TYPE_NONE: + return FF_LOGO_TYPE_NONE; + default: + return FF_LOGO_TYPE_AUTO; + } +} + +static bool applyConfig(FFdata* data, const FFlist* items, bool fullConfig) { + if (data->resultDoc) { + fputs("Error: duplicated `--gen-config` or `--format json` flags found\n", stderr); + return false; + } + + FF_STRBUF_AUTO_DESTROY structure = ffStrbufCreateA(256); + FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED) { + continue; + } + ffStrbufAppendS(&structure, item->baseInfo->name); + ffStrbufAppendC(&structure, ':'); + } + ffStrbufTrimRight(&structure, ':'); + ffStrbufSet(&data->structure, &structure); + + data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; + data->resultDoc = yyjson_mut_doc_new(nullptr); + return true; +} + +[[gnu::cold]] +bool ffGenConfigInteractive(FFdata* data) { + FF_LIST_AUTO_DESTROY modules; + collectModuleInfos(&modules); + + FFGenConfigUI ui = { + .items = ffListCreate(), + .cursor = 0, + .viewOffset = 0, + .logoType = initialLogoType(), + .fullConfig = false, + .confirmingOverwrite = false, + .rows = 24, + .cols = 80, + }; + initItems(&ui.items, &modules, data); + + getTerminalSize(&ui.rows, &ui.cols); + enterRawMode(); + ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), strlen("\e[?1049h\e[?25l"), "\e[?1049h\e[?25l"); + + const bool fileExists = ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY); + int result = runCui(&ui, fileExists); + + restoreRawMode(); + ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), strlen("\e[?1049l\e[?25h\e[m"), "\e[?1049l\e[?25h\e[m"); + + bool success = false; + if (result == 1) { + if (applyConfig(data, &ui.items, ui.fullConfig)) { + instance.config.logo.type = ui.logoType; + success = true; + } + } else { + fputs("\nConfig generation cancelled.\n", stderr); + } + + ffListDestroy(&ui.items); + return success; +} diff --git a/src/common/impl/jsonconfig.c b/src/common/impl/jsonconfig.c index 9feee1bf56..5179d5b5d3 100644 --- a/src/common/impl/jsonconfig.c +++ b/src/common/impl/jsonconfig.c @@ -95,7 +95,7 @@ static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_ for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(type[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrEqualsIgnCase(type, baseInfo->name)) { - uint8_t optionBuf[FF_OPTION_MAX_SIZE]; + alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); if (jsonVal) { baseInfo->parseJsonObject(optionBuf, jsonVal); diff --git a/src/common/impl/netif_gnu.c b/src/common/impl/netif_gnu.c index 6593899a65..5e890dda0b 100644 --- a/src/common/impl/netif_gnu.c +++ b/src/common/impl/netif_gnu.c @@ -6,7 +6,7 @@ #include bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { - FILE* FF_AUTO_CLOSE_FILE netRoute = fopen("/proc/route", "r"); + FF_AUTO_CLOSE_FILE FILE* netRoute = fopen("/proc/route", "r"); if (!netRoute) { return false; diff --git a/src/common/impl/networking_linux.c b/src/common/impl/networking_linux.c index dbee39a69f..770c0ce774 100644 --- a/src/common/impl/networking_linux.c +++ b/src/common/impl/networking_linux.c @@ -457,6 +457,13 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf if (clHeader) { contentLength = (uint32_t) strtoul(clHeader + 15, nullptr, 10); if (contentLength > 0) { + if (contentLength > 1024 * 1024) { // 1MB limit to prevent excessive memory allocation and potential attacks + FF_DEBUG("Content-Length is too large: %u bytes, aborting", contentLength); + close(state->sockfd); + state->sockfd = -1; + return "Content-Length too large"; + } + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin ffStrbufEnsureFree(buffer, contentLength + 16); @@ -481,7 +488,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf return "No HTTP header end found"; } - if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { + if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n") && !ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } diff --git a/src/common/impl/networking_windows.c b/src/common/impl/networking_windows.c index ba4af1dc8b..409edff7e5 100644 --- a/src/common/impl/networking_windows.c +++ b/src/common/impl/networking_windows.c @@ -141,16 +141,14 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho } // Initialize overlapped structure with WSA event for asynchronous I/O - state->overlapped = (OVERLAPPED) { - .hEvent = WSACreateEvent() - }; + state->overlapped = (OVERLAPPED) {}; - if (state->overlapped.hEvent == WSA_INVALID_EVENT) { - FF_DEBUG("WSACreateEvent() failed"); + if (!NT_SUCCESS(NtCreateEvent(&state->overlapped.hEvent, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE))) { + FF_DEBUG("NtCreateEvent() failed"); closesocket(state->sockfd); FreeAddrInfoW(addr); state->sockfd = INVALID_SOCKET; - return "WSACreateEvent() failed"; + return "NtCreateEvent() failed"; } // Build HTTP command @@ -194,7 +192,7 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho if (!result) { if (WSAGetLastError() != WSA_IO_PENDING) { FF_DEBUG("ConnectEx() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); - WSACloseEvent(state->overlapped.hEvent); + NtClose(state->overlapped.hEvent); closesocket(state->sockfd); state->sockfd = INVALID_SOCKET; ffStrbufDestroy(&state->command); @@ -219,37 +217,27 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf return "ffNetworkingSendHttpRequest() failed"; } + DWORD transfer; uint32_t timeout = state->timeout; - if (timeout > 0) { - FF_DEBUG("WSAWaitForMultipleEvents with timeout: %u ms", timeout); - DWORD result = WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, timeout, FALSE); - if (result != WSA_WAIT_EVENT_0) { - if (result == WSA_WAIT_TIMEOUT) { - FF_DEBUG("WSAWaitForMultipleEvents timed out"); - } else { - FF_DEBUG("WSAWaitForMultipleEvents failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); - } - if (CancelIoEx((HANDLE) state->sockfd, &state->overlapped)) { - WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, 10, TRUE); - } - WSACloseEvent(state->overlapped.hEvent); - closesocket(state->sockfd); - ffStrbufDestroy(&state->command); - return "WSAWaitForMultipleEvents() failed or timeout"; + if (!GetOverlappedResultEx((HANDLE) state->sockfd, &state->overlapped, &transfer, timeout > 0 ? timeout : INFINITE, FALSE)) { + DWORD error = GetLastError(); + if (error == WAIT_TIMEOUT) { + FF_DEBUG("GetOverlappedResultEx timed out"); + } else { + FF_DEBUG("GetOverlappedResultEx failed: %s", ffDebugWin32Error(error)); } - } - - DWORD transfer, flags; - if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags)) { - FF_DEBUG("WSAGetOverlappedResult failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + IO_STATUS_BLOCK cancelIosb = {}; + if (NT_SUCCESS(NtCancelIoFileEx((HANDLE) state->sockfd, (PIO_STATUS_BLOCK) &state->overlapped, &cancelIosb))) { + NtWaitForSingleObject(state->overlapped.hEvent, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) 10 * -10000 }); + } + NtClose(state->overlapped.hEvent); closesocket(state->sockfd); - WSACloseEvent(state->overlapped.hEvent); ffStrbufDestroy(&state->command); - return "WSAGetOverlappedResult() failed"; + return "GetOverlappedResultEx() failed or timeout"; } - FF_DEBUG("WSAGetOverlappedResult succeeded, %u bytes sent", (unsigned) transfer); + FF_DEBUG("GetOverlappedResultEx succeeded, %u bytes sent", (unsigned) transfer); ffStrbufDestroy(&state->command); - WSACloseEvent(state->overlapped.hEvent); + NtClose(state->overlapped.hEvent); state->overlapped.hEvent = nullptr; if (setsockopt(state->sockfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nullptr, 0) != 0) { @@ -322,6 +310,13 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf if (clHeader) { contentLength = (uint32_t) strtoul(clHeader + 15, nullptr, 10); if (contentLength > 0) { + if (contentLength > 1024 * 1024) { // 1MB limit to prevent excessive memory allocation and potential attacks + FF_DEBUG("Content-Length is too large: %u bytes, aborting", contentLength); + closesocket(state->sockfd); + state->sockfd = INVALID_SOCKET; + return "Content-Length too large"; + } + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin ffStrbufEnsureFree(buffer, contentLength + 16); @@ -346,7 +341,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf return "No HTTP header end found"; } - if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { + if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n") && !ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } diff --git a/src/common/library.h b/src/common/library.h index 872bfc9fa6..06003cc938 100644 --- a/src/common/library.h +++ b/src/common/library.h @@ -57,6 +57,9 @@ static inline void ffLibraryUnload(void** handle) { #define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, returnValue); + #define FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(library, varName, symbolName) \ + (varName).ff##symbolName = (typeof(&symbolName)) dlsym(library, #symbolName); + #define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, "dlsym " #symbolName " failed"); @@ -94,6 +97,9 @@ void* ffLibraryLoadMulti(const char* path, int maxVersion, ...); #define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, returnValue); + #define FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(library, varName, symbolName) \ + (varName).ff##symbolName = (typeof(&symbolName)) &symbolName; + #define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, "dlsym " #symbolName " failed"); diff --git a/src/common/option.h b/src/common/option.h index 9f54d3c1a2..e2697042c6 100644 --- a/src/common/option.h +++ b/src/common/option.h @@ -34,6 +34,7 @@ typedef struct FFModuleBaseInfo { bool (*generateJsonResult)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* module); // true on success void (*generateJsonConfig)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* obj); FFModuleFormatArgList formatArgs; + const uint8_t defaultOrder; } FFModuleBaseInfo; typedef enum FFModuleKeyType: uint8_t { diff --git a/src/common/unused.h b/src/common/unused.h index 98e21a2e6e..9eb9f7dca4 100644 --- a/src/common/unused.h +++ b/src/common/unused.h @@ -1,6 +1,4 @@ #pragma once -static inline void ffUnused(int dummy, ...) { - (void) dummy; -} -#define FF_UNUSED(...) ffUnused(0, __VA_ARGS__); +static inline void ffUnused(...) { /* no-op */ } +#define FF_UNUSED(...) ffUnused(__VA_ARGS__); diff --git a/src/detection/codec/codec_apple.c b/src/detection/codec/codec_apple.c index 36ca55a992..2cf2a48b2f 100644 --- a/src/detection/codec/codec_apple.c +++ b/src/detection/codec/codec_apple.c @@ -41,7 +41,7 @@ static FFCodecType ffCodecCodecToType(CMVideoCodecType codec) { } static FFCodecType ffCodecDetectEncoders(void) { - CFArrayRef encoderList = nullptr; + FF_CFTYPE_AUTO_RELEASE CFArrayRef encoderList = nullptr; if (VTCopyVideoEncoderList(nullptr, &encoderList) != noErr || !encoderList) { return FF_CODEC_TYPE_NONE; } @@ -65,16 +65,20 @@ static FFCodecType ffCodecDetectEncoders(void) { static FFCodecType ffCodecDetectDecoders() { FFCodecType types = FF_CODEC_TYPE_NONE; for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_CODECS); ++i) { - if (types & FF_CODEC_CODECS[i].type) { + auto codec = FF_CODEC_CODECS[i]; + if (types & codec.type) { continue; } - bool supported = VTIsHardwareDecodeSupported(FF_CODEC_CODECS[i].codec); + if (__builtin_available(macOS 11.0, *)) { + VTRegisterSupplementalVideoDecoderIfAvailable(codec.codec); + } + bool supported = VTIsHardwareDecodeSupported(codec.codec); if (!supported) { continue; } - types |= FF_CODEC_CODECS[i].type; + types |= codec.type; } return types; diff --git a/src/detection/cpu/cpu_apple.c b/src/detection/cpu/cpu_apple.c index 91beb688ef..bce7d55b29 100644 --- a/src/detection/cpu/cpu_apple.c +++ b/src/detection/cpu/cpu_apple.c @@ -25,6 +25,9 @@ static double detectCpuTemp(const FFCPUOptions* options, const FFstrbuf* cpuName case 4: error = ffDetectSmcTemps(FF_TEMP_CPU_M4X, &result); break; + case 5: + error = ffDetectSmcTemps(FF_TEMP_CPU_M5X, &result); + break; default: error = "Unsupported Apple Silicon CPU"; } diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index e3c8018536..6639a70c28 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -25,6 +25,11 @@ static void waylandOutputScaleListener(void* data, [[maybe_unused]] struct wl_ou display->dpi = 96 * (uint32_t) scale; } +static void waylandOutputDoneListener(void* data, [[maybe_unused]] struct wl_output* output) { + WaylandDisplay* display = data; + display->done = true; +} + static void waylandOutputGeometryListener(void* data, [[maybe_unused]] struct wl_output* output, [[maybe_unused]] int32_t x, @@ -49,24 +54,19 @@ static void handleXdgLogicalSize(void* data, [[maybe_unused]] struct zxdg_output } } -// Dirty hack for #477 -// The order of these callbacks MUST follow `struct wl_output_listener` -static void* outputListener[] = { - waylandOutputGeometryListener, // geometry - waylandOutputModeListener, // mode - stubListener, // done - waylandOutputScaleListener, // scale - ffWaylandOutputNameListener, // name - ffWaylandOutputDescriptionListener, // description +static struct wl_output_listener outputListener = { + .geometry = waylandOutputGeometryListener, + .mode = waylandOutputModeListener, + .done = waylandOutputDoneListener, + .scale = waylandOutputScaleListener, + .name = (void*) ffWaylandOutputNameListener, + .description = (void*) ffWaylandOutputDescriptionListener, }; -static_assert( - sizeof(outputListener) >= sizeof(struct wl_output_listener), - "sizeof(outputListener) is too small. Please report it to fastfetch github issue"); static struct zxdg_output_v1_listener zxdgOutputListener = { - .logical_position = (void*) stubListener, + .logical_position = (void*) ffUnused, .logical_size = handleXdgLogicalSize, - .done = (void*) stubListener, + .done = (void*) ffUnused, .name = (void*) ffWaylandOutputNameListener, .description = (void*) ffWaylandOutputDescriptionListener, }; @@ -84,17 +84,17 @@ static void handleWpTfNamed(void *data, [[maybe_unused]] struct wp_image_descrip } static const struct wp_image_description_info_v1_listener wpImageDescInfoListener = { - .done = (void*) stubListener, - .icc_file = (void*) stubListener, - .primaries = (void*) stubListener, - .primaries_named = (void*) stubListener, - .tf_power = (void*) stubListener, + .done = (void*) ffUnused, + .icc_file = (void*) ffUnused, + .primaries = (void*) ffUnused, + .primaries_named = (void*) ffUnused, + .tf_power = (void*) ffUnused, .tf_named = (void*) handleWpTfNamed, - .luminances = (void*) stubListener, - .target_primaries = (void*) stubListener, - .target_luminance = (void*) stubListener, - .target_max_cll = (void*) stubListener, - .target_max_fall = (void*) stubListener, + .luminances = (void*) ffUnused, + .target_primaries = (void*) ffUnused, + .target_luminance = (void*) ffUnused, + .target_max_cll = (void*) ffUnused, + .target_max_fall = (void*) ffUnused, }; const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { @@ -122,6 +122,13 @@ const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip wl_output"; } + if (bindVersion >= WL_OUTPUT_DONE_SINCE_VERSION && !display.done) { + const char* error = ffWaylandWaitForDone(&display); + if (error) { + wldata->ffwl_proxy_destroy(output); + return error; + } + } if (wldata->zxdgOutputManager) { uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION); diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index d26cfbc688..7c49d858cf 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -33,8 +33,8 @@ static const struct kde_output_device_mode_v2_listener modeListener = { .size = waylandKdeModeSizeListener, .refresh = waylandKdeModeRefreshListener, .preferred = waylandKdeModePreferredListener, - .removed = (void*) stubListener, - .flags = (void*) stubListener, + .removed = (void*) ffUnused, + .flags = (void*) ffUnused, }; static void waylandKdeModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) { @@ -57,23 +57,22 @@ static void waylandKdeCurrentModeListener(void* data, [[maybe_unused]] struct kd return; } - int set = 0; + bool foundCurrent = false, foundPreferred = false; FF_LIST_FOR_EACH (WaylandKdeMode, m, *(FFlist*) wldata->internal) { - if (m->pMode == mode) { + if (!foundCurrent && m->pMode == mode) { wldata->width = m->width; wldata->height = m->height; wldata->refreshRate = m->refreshRate; - if (++set == 2) { - break; - } + foundCurrent = true; } - if (m->preferred) { + if (!foundPreferred && m->preferred) { wldata->preferredWidth = m->width; wldata->preferredHeight = m->height; wldata->preferredRefreshRate = m->refreshRate; - if (++set == 2) { - break; - } + foundPreferred = true; + } + if (foundCurrent && foundPreferred) { + break; } } } @@ -147,47 +146,52 @@ static void waylandKdePriorityListener(void* data, [[maybe_unused]] struct kde_o display->primary = priority == 1; } +static void waylandKdeDoneListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2) { + WaylandDisplay* display = data; + display->done = true; +} + static struct kde_output_device_v2_listener outputListener = { .geometry = waylandKdeGeometryListener, .current_mode = waylandKdeCurrentModeListener, .mode = waylandKdeModeListener, - .done = (void*) stubListener, + .done = waylandKdeDoneListener, .scale = waylandKdeScaleListener, .edid = waylandKdeEdidListener, .enabled = waylandKdeEnabledListener, - .uuid = (void*) stubListener, - .serial_number = (void*) stubListener, - .eisa_id = (void*) stubListener, - .capabilities = (void*) stubListener, - .overscan = (void*) stubListener, - .vrr_policy = (void*) stubListener, - .rgb_range = (void*) stubListener, + .uuid = (void*) ffUnused, + .serial_number = (void*) ffUnused, + .eisa_id = (void*) ffUnused, + .capabilities = (void*) ffUnused, + .overscan = (void*) ffUnused, + .vrr_policy = (void*) ffUnused, + .rgb_range = (void*) ffUnused, .name = waylandKdeNameListener, .high_dynamic_range = waylandKdeHdrListener, - .sdr_brightness = (void*) stubListener, - .wide_color_gamut = (void*) stubListener, - .auto_rotate_policy = (void*) stubListener, - .icc_profile_path = (void*) stubListener, - .brightness_metadata = (void*) stubListener, - .brightness_overrides = (void*) stubListener, - .sdr_gamut_wideness = (void*) stubListener, - .color_profile_source = (void*) stubListener, - .brightness = (void*) stubListener, - .color_power_tradeoff = (void*) stubListener, - .dimming = (void*) stubListener, - .replication_source = (void*) stubListener, - .ddc_ci_allowed = (void*) stubListener, + .sdr_brightness = (void*) ffUnused, + .wide_color_gamut = (void*) ffUnused, + .auto_rotate_policy = (void*) ffUnused, + .icc_profile_path = (void*) ffUnused, + .brightness_metadata = (void*) ffUnused, + .brightness_overrides = (void*) ffUnused, + .sdr_gamut_wideness = (void*) ffUnused, + .color_profile_source = (void*) ffUnused, + .brightness = (void*) ffUnused, + .color_power_tradeoff = (void*) ffUnused, + .dimming = (void*) ffUnused, + .replication_source = (void*) ffUnused, + .ddc_ci_allowed = (void*) ffUnused, .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener, - .max_bits_per_color_range = (void*) stubListener, - .automatic_max_bits_per_color_limit = (void*) stubListener, - .edr_policy = (void*) stubListener, - .sharpness = (void*) stubListener, + .max_bits_per_color_range = (void*) ffUnused, + .automatic_max_bits_per_color_limit = (void*) ffUnused, + .edr_policy = (void*) ffUnused, + .sharpness = (void*) ffUnused, .priority = waylandKdePriorityListener, - .auto_brightness = (void*) stubListener, - .removed = (void*) stubListener, - .hdr_icc_profile_path = (void*) stubListener, - .hdr_color_profile_source = (void*) stubListener, - .abm_level = (void*) stubListener, + .auto_brightness = (void*) ffUnused, + .removed = (void*) ffUnused, + .hdr_icc_profile_path = (void*) ffUnused, + .hdr_color_profile_source = (void*) ffUnused, + .abm_level = (void*) ffUnused, }; static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* output) { @@ -211,6 +215,13 @@ static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip kde_output_device_v2"; } + if (!display.done) { + const char* error = ffWaylandWaitForDone(&display); + if (error) { + wldata->ffwl_proxy_destroy(output); + return error; + } + } // Destroy any mode proxies that were created during the listeners. // wl proxies created for modes are not automatically freed by destroying // the parent output proxy, so destroy them explicitly to avoid leaks. @@ -288,7 +299,7 @@ static void waylandKdeOutputListener(void* data, [[maybe_unused]] struct kde_out static struct kde_output_device_registry_v2_listener registryListener = { .output = waylandKdeOutputListener, - .finished = (void*) stubListener, + .finished = (void*) ffUnused, }; const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c index 7e37aee8dd..29c1d46e9b 100644 --- a/src/detection/displayserver/linux/wayland/wayland.c +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -217,6 +217,39 @@ uint32_t ffWaylandHandleRotation(WaylandDisplay* display) { return rotation; } +const char* ffWaylandWaitForDone(WaylandDisplay* display) { + int32_t timeout = instance.config.general.processingTimeout; + struct timespec ts; + if (timeout == 0) { + return "timeout is disabled"; + } else if (timeout > 0) { + if (!display->parent->ffwl_display_dispatch_timeout) { + return "timeout is enabled, but the libwayland-client version is too old to support it"; + } + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + } + + WaylandData* wldata = display->parent; + + // Some compositors emit the final output state asynchronously after the roundtrip callback (#2074) + while (!display->done) { + if (timeout > 0) { + if (wldata->ffwl_display_dispatch_timeout(wldata->display, &ts) < 0) { + return "wl_display_dispatch_timeout() failed"; + } + } else if (timeout < 0) { + if (wldata->ffwl_display_dispatch(wldata->display) < 0) { + return "wl_display_dispatch() failed"; + } + } else { + return "Timeout exceeded, but the compositor did not send a done event"; + } + } + + return nullptr; +} + const char* ffdsConnectWayland(FFDisplayServerResult* result) { if (getenv("XDG_RUNTIME_DIR") == nullptr) { return "Wayland requires $XDG_RUNTIME_DIR being set"; @@ -236,6 +269,12 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_destroy) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_display_roundtrip) + if (instance.config.general.processingTimeout < 0) { + FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch) + } else if (instance.config.general.processingTimeout > 0) { + FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch_timeout) + } + data.display = ffwl_display_connect(nullptr); if (data.display == nullptr) { return "wl_display_connect returned nullptr"; @@ -253,7 +292,7 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { struct wl_registry_listener registry_listener = { .global = waylandGlobalAddListener, - .global_remove = (void*) stubListener + .global_remove = (void*) ffUnused }; data.ffwl_proxy_add_listener(registry, (void (**)(void)) ®istry_listener, &data); diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index a661936cf0..e225eb881b 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -20,12 +20,19 @@ typedef enum WaylandProtocolType: uint8_t { FF_WAYLAND_PROTOCOL_TYPE_KDE_REGISTRY, } WaylandProtocolType; +// In case users have an ancient version of libwayland-client +int wl_display_dispatch_timeout(struct wl_display *display, const struct timespec *timeout); + typedef struct WaylandData { FFDisplayServerResult* result; FF_LIBRARY_SYMBOL(wl_proxy_marshal_constructor_versioned) FF_LIBRARY_SYMBOL(wl_proxy_add_listener) FF_LIBRARY_SYMBOL(wl_proxy_destroy) FF_LIBRARY_SYMBOL(wl_display_roundtrip) + union { + FF_LIBRARY_SYMBOL(wl_display_dispatch) + FF_LIBRARY_SYMBOL(wl_display_dispatch_timeout) + }; struct wl_display* display; WaylandProtocolType protocolType; struct wl_proxy* zxdgOutputManager; @@ -58,12 +65,9 @@ typedef struct WaylandDisplay { FFstrbuf serial; uint8_t bitDepth; bool primary; + bool done; } WaylandDisplay; -inline static void stubListener(void* data, ...) { - (void) data; -} - inline static uint64_t ffWaylandGenerateIdFromName(const char* name) { uint64_t id = 0; size_t len = strlen(name); @@ -79,6 +83,7 @@ void ffWaylandOutputNameListener(void* data, [[maybe_unused]] void* output, cons void ffWaylandOutputDescriptionListener(void* data, [[maybe_unused]] void* output, const char* description); // Modifies content of display. Don't call this function when calling ffdsAppendDisplay uint32_t ffWaylandHandleRotation(WaylandDisplay* display); +const char* ffWaylandWaitForDone(WaylandDisplay* display); const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); diff --git a/src/detection/gpu/gpu_apple.c b/src/detection/gpu/gpu_apple.c index 4942252987..f3775bc5a3 100644 --- a/src/detection/gpu/gpu_apple.c +++ b/src/detection/gpu/gpu_apple.c @@ -28,6 +28,9 @@ static double detectGpuTemp(const FFstrbuf* gpuName) { case 4: error = ffDetectSmcTemps(FF_TEMP_GPU_M4X, &result); break; + case 5: + error = ffDetectSmcTemps(FF_TEMP_GPU_M5X, &result); + break; default: error = "Unsupported Apple Silicon GPU"; break; diff --git a/src/detection/gpu/gpu_windows.c b/src/detection/gpu/gpu_windows.c index 9a6b95ad7e..526ff66a5d 100644 --- a/src/detection/gpu/gpu_windows.c +++ b/src/detection/gpu/gpu_windows.c @@ -17,12 +17,16 @@ #include #include #include + #include #include #define GUID_DEVCLASS_DISPLAY_STRING L"{4d36e968-e325-11ce-bfc1-08002be10318}" // Found in -static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds, bool queryPcieGen) { - FF_DEBUG("Query PCI device info: %08llX", gpu->deviceId); +// https://wine-devel.winehq.narkive.com/vKO2Bkgj/patch-1-2-dxgi-tests-add-test-for-enumerating-display-adapters-using-setupapi +DEFINE_DEVPROPKEY(DEVPROPKEY_DISPLAY_ADAPTER_LUID, 0x60b193cb, 0x5276, 0x4d0f, 0x96, 0xfc, 0xf1, 0x73, 0xab, 0xad, 0x3e, 0xc6, 2); + +static bool queryDeviceInfoCM(FFGPUResult* gpu, uint64_t luid, D3DKMT_DEVICE_IDS* outDeviceIds, bool queryPcieGen) { + FF_DEBUG("Query PCI device info: %08llX with LUID: %08llX", gpu->deviceId, luid); static FFlist deviceIdsCache; static bool initialized; @@ -32,7 +36,10 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds uint32_t maxLinkSpeed; uint32_t maxLinkWidth; D3DKMT_DEVICE_IDS deviceIds; - uint64_t adapterAddress; + uint64_t pciAddr; + uint64_t luid; + FFstrbuf vendor; + FFstrbuf name; } CacheEntry; if (!initialized) { @@ -71,13 +78,36 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } } - if (wcsncmp(devId, L"PCI\\", 4) != 0) { - FF_DEBUG("Skipping non-PCI device ID: %ls", devId); - continue; - } - CacheEntry* entry = FF_LIST_ADD(CacheEntry, deviceIdsCache); - *entry = (CacheEntry) {}; + *entry = (CacheEntry){}; + + ULONG bufLen = sizeof(entry->luid); + DEVPROPTYPE type; + CONFIGRET ret = CM_Get_DevNode_PropertyW(devInst, &DEVPROPKEY_DISPLAY_ADAPTER_LUID, &type, (PBYTE) &entry->luid, &bufLen, 0); + if (ret != CR_SUCCESS) { // Not available on Windows 8.1 + FF_DEBUG("Failed to get device LUID: %s", ffDebugConfigRet(ret)); + + uint32_t pciBus = 0; + ULONG pciBufLen = sizeof(pciBus); + if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, nullptr, &pciBus, &pciBufLen, 0) == CR_SUCCESS) { + uint32_t pciAddr = 0; + pciBufLen = sizeof(pciAddr); + if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, nullptr, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) { + entry->pciAddr = ffGPUPciAddr2Id(0, pciBus, (pciAddr >> 16) & 0xFFFF, pciAddr & 0xFFFF); + FF_DEBUG("Cached device IDs for PCI bus %u: vendor=0x%04x device=0x%04x", pciBus, entry->deviceIds.VendorID, entry->deviceIds.DeviceID); + } else { + FF_DEBUG("Failed to get PCI address"); + } + } else { + FF_DEBUG("Failed to get PCI bus number"); + } + + if (entry->pciAddr == 0) { + FF_DEBUG("Skipping device ID: %ls due to missing LUID and PCI address", devId); + deviceIdsCache.length--; + continue; + } + } // L"PCI\\VEN_10DE&DEV_2782&SUBSYS_513417AA&REV_A1\\4&3674a6b9&0&0008" if (swscanf(devId + 4, L"VEN_%x&DEV_%x&SUBSYS_%4x%4x&REV_%x", &entry->deviceIds.VendorID, &entry->deviceIds.DeviceID, &entry->deviceIds.SubSystemID, &entry->deviceIds.SubVendorID, &entry->deviceIds.RevisionID) >= 2) { @@ -87,32 +117,44 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds // And yeah, DXGKMDT_OPM_BUS_TYPE_PCIEXPRESS (3) exists entry->deviceIds.BusType = 1; } else { - FF_DEBUG("Failed to parse PCI IDs from device ID string"); - deviceIdsCache.length--; // remove the cache entry since it's not valid - continue; + FF_DEBUG("Failed to parse PCI IDs from device ID: %ls", devId); + entry->deviceIds.VendorID = -1u; } - uint32_t pciBus = 0; - ULONG pciBufLen = sizeof(pciBus); - if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, nullptr, &pciBus, &pciBufLen, 0) == CR_SUCCESS) { - uint32_t pciAddr = 0; - pciBufLen = sizeof(pciAddr); - if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, nullptr, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) { - entry->adapterAddress = ffGPUPciAddr2Id(0, pciBus, (pciAddr >> 16) & 0xFFFF, pciAddr & 0xFFFF); - FF_DEBUG("Cached device IDs for PCI bus %u: vendor=0x%04x device=0x%04x", pciBus, entry->deviceIds.VendorID, entry->deviceIds.DeviceID); + wchar_t wstr[256]; + bufLen = sizeof(wstr); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_FriendlyName, &type, (PBYTE) wstr, &bufLen, 0); + if (ret == CR_SUCCESS) { + ffStrbufSetNWS(&entry->name, (bufLen - 1) / sizeof(wchar_t), wstr); + FF_DEBUG("Device friendly name: %ls", wstr); + } else { + FF_DEBUG("Failed to get device friendly name: %s", ffDebugConfigRet(ret)); + + bufLen = sizeof(wstr); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_DeviceDesc, &type, (PBYTE) wstr, &bufLen, 0); + if (ret == CR_SUCCESS) { + ffStrbufSetNWS(&entry->name, (bufLen - 1) / sizeof(wchar_t), wstr); + FF_DEBUG("Device description: %ls", wstr); } else { - FF_DEBUG("Failed to get PCI address"); + FF_DEBUG("Failed to get device description: %s", ffDebugConfigRet(ret)); } + } + + bufLen = sizeof(wstr); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_Manufacturer, &type, (PBYTE) wstr, &bufLen, 0); + if (ret == CR_SUCCESS) { + ffStrbufSetNWS(&entry->vendor, (bufLen - 1) / sizeof(wchar_t), wstr); + FF_DEBUG("Device vendor name: %ls", wstr); } else { - FF_DEBUG("Failed to get PCI bus number"); + FF_DEBUG("Failed to get device vendor name: %s", ffDebugConfigRet(ret)); } - if (queryPcieGen) { + if (queryPcieGen && entry->deviceIds.VendorID != -1u) { DEVPROPTYPE propType; - pciBufLen = sizeof(entry->maxLinkSpeed); + bufLen = sizeof(entry->maxLinkSpeed); // Reports PCIe gen despite the PKEY name - CONFIGRET ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkSpeed, &propType, (PBYTE) &entry->maxLinkSpeed, &pciBufLen, 0); + CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkSpeed, &propType, (PBYTE) &entry->maxLinkSpeed, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe max GEN: %u", entry->maxLinkSpeed); } else { @@ -120,8 +162,8 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } if (entry->maxLinkSpeed != FF_GPU_PCIE_SPEED_UNSET) { - pciBufLen = sizeof(entry->maxLinkWidth); - ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkWidth, &propType, (PBYTE) &entry->maxLinkWidth, &pciBufLen, 0); + bufLen = sizeof(entry->maxLinkWidth); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkWidth, &propType, (PBYTE) &entry->maxLinkWidth, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe max link width: %u", entry->maxLinkWidth); } else { @@ -129,8 +171,8 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } } - pciBufLen = sizeof(entry->currentLinkSpeed); - ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkSpeed, &propType, (PBYTE) &entry->currentLinkSpeed, &pciBufLen, 0); + bufLen = sizeof(entry->currentLinkSpeed); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkSpeed, &propType, (PBYTE) &entry->currentLinkSpeed, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe GEN: %u", entry->currentLinkSpeed); } else { @@ -138,8 +180,8 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } if (entry->currentLinkSpeed != FF_GPU_PCIE_SPEED_UNSET) { - pciBufLen = sizeof(entry->currentLinkWidth); - ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkWidth, &propType, (PBYTE) &entry->currentLinkWidth, &pciBufLen, 0); + bufLen = sizeof(entry->currentLinkWidth); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkWidth, &propType, (PBYTE) &entry->currentLinkWidth, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe current link width: %u", entry->currentLinkWidth); } else { @@ -151,12 +193,31 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } FF_LIST_FOR_EACH (CacheEntry, entry, deviceIdsCache) { - if (gpu->deviceId == entry->adapterAddress) { - FF_DEBUG("Cache hit for adapter address: %08llX", gpu->deviceId); + if (luid == entry->luid || entry->pciAddr == gpu->deviceId) { + FF_DEBUG("Cache hit for adapter LUID: %08llX", luid); if (outDeviceIds->VendorID == -1u) { *outDeviceIds = entry->deviceIds; } + if (gpu->vendor.length == 0) { + if (entry->deviceIds.VendorID != -1u) { + ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(entry->deviceIds.VendorID)); + } + if (gpu->vendor.length == 0 && entry->vendor.length > 0) { + ffStrbufDestroy(&gpu->vendor); + ffStrbufInitMove(&gpu->vendor, &entry->vendor); + } + } else if (gpu->name.length == 0 && entry->deviceIds.VendorID == -1u) { + // Some Indirect display adapters reports fake Device IDs + ffStrbufDestroy(&gpu->vendor); + ffStrbufInitMove(&gpu->vendor, &entry->vendor); + } + + if (gpu->name.length == 0) { + ffStrbufDestroy(&gpu->name); + ffStrbufInitMove(&gpu->name, &entry->name); + } + if (queryPcieGen) { gpu->psMax.gen = (uint16_t) entry->maxLinkSpeed; gpu->psMax.lanes = (uint16_t) entry->maxLinkWidth; @@ -167,37 +228,10 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } } - FF_DEBUG("Cache miss for adapter address: %08llX", gpu->deviceId); + FF_DEBUG("Cache miss for adapter LUID: %08llX", luid); return false; } -static bool queryVendorNameViaRegistry(FFstrbuf* vendor, D3DKMT_HANDLE hAdapter) { - // `KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID` reports the GUID value used by the adapter's registry key (DirectX and Video) - - GUID guid; - NTSTATUS status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { - .hAdapter = hAdapter, - .Type = KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID, - .pPrivateDriverData = &guid, - .PrivateDriverDataSize = sizeof(guid), - }); - if (!NT_SUCCESS(status)) { - FF_DEBUG("Failed to query adapter unique GUID: %s", ffDebugNtStatus(status)); - return false; - } - - wchar_t path[PATH_MAX]; - swprintf(path, ARRAY_SIZE(path), L"SYSTEM\\CurrentControlSet\\Control\\Video\\{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\\0000", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - - FF_DEBUG("Querying registry: HKEY_LOCAL_MACHINE\\%ls\\ProviderName", path); - FF_AUTO_CLOSE_FD HANDLE key = nullptr; - if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, path, &key, nullptr)) { - return false; - } - - return ffRegReadStrbuf(key, L"ProviderName", vendor, nullptr); -} - #else #include #include @@ -301,6 +335,8 @@ ffGPUDetectWsl2 goto close_adapter; } + uint64_t luid = ((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart; + FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus); ffStrbufInit(&gpu->vendor); ffStrbufInit(&gpu->name); @@ -321,7 +357,7 @@ ffGPUDetectWsl2 : FF_GPU_TYPE_UNKNOWN; gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET; - D3DKMT_DRIVERVERSION wddmVersion = KMT_DRIVERVERSION_WDDM_2_0; + D3DKMT_DRIVERVERSION wddmVersion; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, .Type = KMTQAITYPE_DRIVERVERSION, @@ -332,6 +368,7 @@ ffGPUDetectWsl2 ffStrbufSetF(&gpu->platformApi, "WDDM %u.%u", (uint32_t) wddmVersion / 1000, ((uint32_t) wddmVersion % 1000) / 100); FF_DEBUG("Adapter #%u WDDM version: %u", i, (uint32_t) wddmVersion); } else { + wddmVersion = KMT_DRIVERVERSION_WDDM_1_3; // Smallest supported WDDM version, used for fallback ffStrbufSetStatic(&gpu->platformApi, "WDDM"); FF_DEBUG("KMTQAITYPE_DRIVERVERSION query failed for adapter #%u", i); } @@ -352,7 +389,7 @@ ffGPUDetectWsl2 adapterAddress.FunctionNumber); } else { adapterAddress.BusNumber = -1u; - gpu->deviceId = ffGPUGeneral2Id(((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart); + gpu->deviceId = ffGPUGeneral2Id(luid); FF_DEBUG("KMTQAITYPE_ADAPTERADDRESS query failed for adapter #%u, fallback to LUID-based deviceId: %s", i, ffDebugNtStatus(status)); @@ -376,14 +413,6 @@ ffGPUDetectWsl2 FF_DEBUG("KMTQAITYPE_PHYSICALADAPTERDEVICEIDS query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); } - #if _WIN32 && FF_WIN81_COMPAT - if (adapterAddress.BusNumber != -1u && (deviceIds.DeviceIds.VendorID == -1u || options->driverSpecific)) { - if (queryPciDeviceInfo(gpu, &deviceIds.DeviceIds, options->driverSpecific) && gpu->vendor.length == 0) { - ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(deviceIds.DeviceIds.VendorID)); - } - } - #endif - D3DKMT_UMD_DRIVER_VERSION umdDriverVersion; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, @@ -403,63 +432,55 @@ ffGPUDetectWsl2 FF_DEBUG("KMTQAITYPE_UMD_DRIVER_VERSION query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); } - typeof(&ffDetectNvidiaGpuInfo) detectFn; - const char* dllName; - if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName)) { - FF_DEBUG("Calling driver-specific detection function for vendor: %s, DLL: %s", gpu->vendor.chars, dllName); - [[maybe_unused]] const char* error = detectFn( - &(FFGpuDriverCondition) { - .type = FF_GPU_DRIVER_CONDITION_TYPE_LUID | - (deviceIds.DeviceIds.VendorID != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) | - (adapterAddress.BusNumber != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0), - .pciDeviceId = { - .deviceId = deviceIds.DeviceIds.DeviceID, - .vendorId = deviceIds.DeviceIds.VendorID, - .subSystemId = deviceIds.DeviceIds.SubSystemID, - .revId = deviceIds.DeviceIds.RevisionID, - }, - .pciBusId = { - .domain = 0, - .bus = adapterAddress.BusNumber, - .device = adapterAddress.DeviceNumber, - .func = adapterAddress.FunctionNumber, - }, - .luid = ((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart, - }, - (FFGpuDriverResult) { - .index = &gpu->index, - .temp = options->temp ? &gpu->temperature : nullptr, - .memory = options->driverSpecific ? &gpu->dedicated : nullptr, - .sharedMemory = options->driverSpecific ? &gpu->shared : nullptr, - .memoryType = options->driverSpecific ? &gpu->memoryType : nullptr, - .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : nullptr, - .coreUsage = options->driverSpecific ? &gpu->coreUsage : nullptr, - .type = &gpu->type, - .frequency = options->driverSpecific ? &gpu->frequency : nullptr, - .name = &gpu->name, - .psCurr = options->driverSpecific ? &gpu->psCurr : nullptr, - .psMax = options->driverSpecific ? &gpu->psMax : nullptr, - }, - dllName); - FF_DEBUG("Driver-specific detection completed: %s", error ?: "Success"); - } else if (options->driverSpecific) { - FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars); - } - -#if _WIN32 - // Put this after the driver-specific detection, as `getDriverSpecificDetectionFn` never succeeds - if (gpu->vendor.length == 0 && wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { - // For non-PCI devices - FF_DEBUG("Attempting to query vendor name via registry for adapter #%u", i); - queryVendorNameViaRegistry(&gpu->vendor, adapter->hAdapter); + bool isIndirectDisplayDevice = gpu->type == FF_GPU_TYPE_UNKNOWN && adapterType.IndirectDisplayDevice; + if (isIndirectDisplayDevice) { + FF_DEBUG("Adapter #%u is an indirect display device", i); } - #if !FF_WIN81_COMPAT - if (adapterAddress.BusNumber != -1u && options->driverSpecific && gpu->pcieSpeed == FF_GPU_PCIE_SPEED_UNSET) { - queryPciDeviceInfo(gpu, &deviceIds.DeviceIds, options->driverSpecific); - } - #endif -#endif + if (!isIndirectDisplayDevice) { + typeof(&ffDetectNvidiaGpuInfo) detectFn; + const char* dllName; + if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName)) { + FF_DEBUG("Calling driver-specific detection function for vendor: %s, DLL: %s", gpu->vendor.chars, dllName); + [[maybe_unused]] const char* error = detectFn( + &(FFGpuDriverCondition) { + .type = FF_GPU_DRIVER_CONDITION_TYPE_LUID | + (deviceIds.DeviceIds.VendorID != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) | + (adapterAddress.BusNumber != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0), + .pciDeviceId = { + .deviceId = deviceIds.DeviceIds.DeviceID, + .vendorId = deviceIds.DeviceIds.VendorID, + .subSystemId = deviceIds.DeviceIds.SubSystemID, + .revId = deviceIds.DeviceIds.RevisionID, + }, + .pciBusId = { + .domain = 0, + .bus = adapterAddress.BusNumber, + .device = adapterAddress.DeviceNumber, + .func = adapterAddress.FunctionNumber, + }, + .luid = luid, + }, + (FFGpuDriverResult) { + .index = &gpu->index, + .temp = options->temp ? &gpu->temperature : nullptr, + .memory = options->driverSpecific ? &gpu->dedicated : nullptr, + .sharedMemory = options->driverSpecific ? &gpu->shared : nullptr, + .memoryType = options->driverSpecific ? &gpu->memoryType : nullptr, + .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : nullptr, + .coreUsage = options->driverSpecific ? &gpu->coreUsage : nullptr, + .type = &gpu->type, + .frequency = options->driverSpecific ? &gpu->frequency : nullptr, + .name = &gpu->name, + .psCurr = options->driverSpecific ? &gpu->psCurr : nullptr, + .psMax = options->driverSpecific ? &gpu->psMax : nullptr, + }, + dllName); + FF_DEBUG("Driver-specific detection completed: %s", error ?: "Success"); + } else if (options->driverSpecific) { + FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars); + } + }; if (gpu->name.length == 0) { D3DKMT_ADAPTERREGISTRYINFO registryInfo; @@ -477,148 +498,156 @@ ffGPUDetectWsl2 } } - if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET && gpu->shared.total == FF_GPU_VMEM_SIZE_UNSET) { - if (wddmVersion >= KMT_DRIVERVERSION_WDDM_3_1 && options->driverSpecific) { - // Supports memory usage query; requires Windows 11 (22H2) or later - D3DKMT_QUERYSTATISTICS queryStatistics = { - .Type = D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE, - .AdapterLuid = adapter->AdapterLuid, - .QuerySegmentGroupUsage = { - .PhysicalAdapterIndex = 0, - .SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL, - }, - }; - status = D3DKMTQueryStatistics(&queryStatistics); - if (NT_SUCCESS(status)) { - D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; - uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; - uint64_t total = used + info->FreeBytes + info->ZeroBytes; - gpu->dedicated.used = used; - gpu->dedicated.total = total; - FF_DEBUG("Adapter #%u local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); - } else { - FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s", - i, - ffDebugNtStatus(status)); - } - - queryStatistics.QuerySegmentGroupUsage.SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL; - status = D3DKMTQueryStatistics(&queryStatistics); - if (NT_SUCCESS(status)) { - D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; - uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; - uint64_t total = used + info->FreeBytes + info->ZeroBytes; - gpu->shared.used = used; - gpu->shared.total = total; - FF_DEBUG("Adapter #%u non-local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); - } else { - FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s", - i, - ffDebugNtStatus(status)); - } - } else { - // Supports basic segment (total) size query - D3DKMT_SEGMENTSIZEINFO segmentSizeInfo = {}; - status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { - .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_GETSEGMENTSIZE, - .pPrivateDriverData = &segmentSizeInfo, - .PrivateDriverDataSize = sizeof(segmentSizeInfo), - }); - if (NT_SUCCESS(status)) { - FF_DEBUG("Adapter #%u segment size - DedicatedVideoMemorySize: %" PRIu64 - ", DedicatedSystemMemorySize: %" PRIu64 ", SharedSystemMemorySize: %" PRIu64, - i, - (uint64_t) segmentSizeInfo.DedicatedVideoMemorySize, - (uint64_t) segmentSizeInfo.DedicatedSystemMemorySize, - (uint64_t) segmentSizeInfo.SharedSystemMemorySize); - gpu->dedicated.total = segmentSizeInfo.DedicatedVideoMemorySize; - gpu->shared.total = segmentSizeInfo.DedicatedSystemMemorySize + segmentSizeInfo.SharedSystemMemorySize; - } else { - FF_DEBUG("Failed to query segment size information for adapter #%u: %s", i, ffDebugNtStatus(status)); - } - } + #if _WIN32 + if (gpu->name.length == 0 || gpu->vendor.length == 0 || (options->driverSpecific && gpu->pcieSpeed == FF_GPU_PCIE_SPEED_UNSET)) { + queryDeviceInfoCM(gpu, luid, &deviceIds.DeviceIds, options->driverSpecific); } + #endif - if (wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { - if (gpu->frequency == FF_GPU_FREQUENCY_UNSET) { - for (uint32_t nodeIdx = 0;; nodeIdx++) { - D3DKMT_NODEMETADATA nodeMetadata = { - .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx, + if (!isIndirectDisplayDevice) { + if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET && gpu->shared.total == FF_GPU_VMEM_SIZE_UNSET) { + if (wddmVersion >= KMT_DRIVERVERSION_WDDM_3_1 && options->driverSpecific) { + // Supports memory usage query; requires Windows 11 (22H2) or later + D3DKMT_QUERYSTATISTICS queryStatistics = { + .Type = D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE, + .AdapterLuid = adapter->AdapterLuid, + .QuerySegmentGroupUsage = { + .PhysicalAdapterIndex = 0, + .SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL, + }, }; + status = D3DKMTQueryStatistics(&queryStatistics); + if (NT_SUCCESS(status)) { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; + uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; + uint64_t total = used + info->FreeBytes + info->ZeroBytes; + gpu->dedicated.used = used; + gpu->dedicated.total = total; + FF_DEBUG("Adapter #%u local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); + } else { + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); + } + + queryStatistics.QuerySegmentGroupUsage.SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL; + status = D3DKMTQueryStatistics(&queryStatistics); + if (NT_SUCCESS(status)) { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; + uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; + uint64_t total = used + info->FreeBytes + info->ZeroBytes; + gpu->shared.used = used; + gpu->shared.total = total; + FF_DEBUG("Adapter #%u non-local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); + } else { + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); + } + } else { + // Supports basic segment (total) size query + D3DKMT_SEGMENTSIZEINFO segmentSizeInfo = {}; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_NODEMETADATA, - .pPrivateDriverData = &nodeMetadata, - .PrivateDriverDataSize = sizeof(nodeMetadata), + .Type = KMTQAITYPE_GETSEGMENTSIZE, + .pPrivateDriverData = &segmentSizeInfo, + .PrivateDriverDataSize = sizeof(segmentSizeInfo), }); - if (!NT_SUCCESS(status)) { break; } + if (NT_SUCCESS(status)) { + FF_DEBUG("Adapter #%u segment size - DedicatedVideoMemorySize: %" PRIu64 + ", DedicatedSystemMemorySize: %" PRIu64 ", SharedSystemMemorySize: %" PRIu64, + i, + (uint64_t) segmentSizeInfo.DedicatedVideoMemorySize, + (uint64_t) segmentSizeInfo.DedicatedSystemMemorySize, + (uint64_t) segmentSizeInfo.SharedSystemMemorySize); + gpu->dedicated.total = segmentSizeInfo.DedicatedVideoMemorySize; + gpu->shared.total = segmentSizeInfo.DedicatedSystemMemorySize + segmentSizeInfo.SharedSystemMemorySize; + } else { + FF_DEBUG("Failed to query segment size information for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + } + } - if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { continue; } + if (wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { + if (gpu->frequency == FF_GPU_FREQUENCY_UNSET) { + for (uint32_t nodeIdx = 0;; nodeIdx++) { + D3DKMT_NODEMETADATA nodeMetadata = { + .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_NODEMETADATA, + .pPrivateDriverData = &nodeMetadata, + .PrivateDriverDataSize = sizeof(nodeMetadata), + }); + if (!NT_SUCCESS(status)) { break; } + + if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { continue; } + + D3DKMT_NODE_PERFDATA nodePerfData = { + .NodeOrdinal = nodeIdx, + .PhysicalAdapterIndex = 0, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_NODEPERFDATA, + .pPrivateDriverData = &nodePerfData, + .PrivateDriverDataSize = sizeof(nodePerfData), + }); + if (NT_SUCCESS(status)) { + if (nodePerfData.MaxFrequency != 0) { + gpu->frequency = (uint32_t) (nodePerfData.MaxFrequency / 1000 / 1000); + FF_DEBUG("Adapter #%u max graphics frequency: %u MHz", i, gpu->frequency); + } else { + FF_DEBUG("Adapter #%u does not report max graphics frequency", i); + } + break; + } else { + FF_DEBUG("Failed to query node performance data for adapter #%u node #%u: %s", + i, + nodeIdx, + ffDebugNtStatus(status)); + } + } + } - D3DKMT_NODE_PERFDATA nodePerfData = { - .NodeOrdinal = nodeIdx, + if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET) { + D3DKMT_ADAPTER_PERFDATA adapterPerfData = { .PhysicalAdapterIndex = 0, }; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_NODEPERFDATA, - .pPrivateDriverData = &nodePerfData, - .PrivateDriverDataSize = sizeof(nodePerfData), + .Type = KMTQAITYPE_ADAPTERPERFDATA, + .pPrivateDriverData = &adapterPerfData, + .PrivateDriverDataSize = sizeof(adapterPerfData), }); if (NT_SUCCESS(status)) { - if (nodePerfData.MaxFrequency != 0) { - gpu->frequency = (uint32_t) (nodePerfData.MaxFrequency / 1000 / 1000); - FF_DEBUG("Adapter #%u max graphics frequency: %u MHz", i, gpu->frequency); + if (adapterPerfData.Temperature != 0) { + gpu->temperature = adapterPerfData.Temperature / 10.0; + FF_DEBUG("Adapter #%u temperature: %.1f°C", i, gpu->temperature); } else { - FF_DEBUG("Adapter #%u does not report max graphics frequency", i); + FF_DEBUG("Adapter #%u does not report temperature data", i); } - break; } else { - FF_DEBUG("Failed to query node performance data for adapter #%u node #%u: %s", - i, - nodeIdx, - ffDebugNtStatus(status)); + FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status)); } } } - if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET) { - D3DKMT_ADAPTER_PERFDATA adapterPerfData = { - .PhysicalAdapterIndex = 0, - }; - status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { - .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_ADAPTERPERFDATA, - .pPrivateDriverData = &adapterPerfData, - .PrivateDriverDataSize = sizeof(adapterPerfData), - }); - if (NT_SUCCESS(status)) { - if (adapterPerfData.Temperature != 0) { - gpu->temperature = adapterPerfData.Temperature / 10.0; - FF_DEBUG("Adapter #%u temperature: %.1f°C", i, gpu->temperature); - } else { - FF_DEBUG("Adapter #%u does not report temperature data", i); - } - } else { - FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status)); + if (gpu->type == FF_GPU_TYPE_UNKNOWN) { + if (ffGPUDetectTypeByVendorAndName(gpu)) { + // OK + } + #if _WIN32 + else if (ffIsWindows10OrGreater()) { + const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult * gpu); + [[maybe_unused]] const char* error = ffGPUDetectTypeWithDXCore(adapter->AdapterLuid, gpu); + FF_DEBUG("DXCore GPU type detection result: %s", error ?: "Success"); + } + #endif + else { + FF_DEBUG("Unable to determine GPU type by any method for this adapter"); } - } - } - - if (gpu->type == FF_GPU_TYPE_UNKNOWN) { - if (ffGPUDetectTypeByVendorAndName(gpu)) { - // OK - } -#if _WIN32 - else if (ffIsWindows10OrGreater()) { - const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult * gpu); - [[maybe_unused]] const char* error = ffGPUDetectTypeWithDXCore(adapter->AdapterLuid, gpu); - FF_DEBUG("DXCore GPU type detection result: %s", error ?: "Success"); - } -#endif - else { - FF_DEBUG("Unable to determine GPU type by any method for this adapter"); } } diff --git a/src/detection/initsystem/initsystem_windows.c b/src/detection/initsystem/initsystem_windows.c new file mode 100644 index 0000000000..f41a66cfa2 --- /dev/null +++ b/src/detection/initsystem/initsystem_windows.c @@ -0,0 +1,38 @@ +#include "initsystem.h" +#include "common/windows/unicode.h" +#include "common/windows/nt.h" +#include "common/windows/version.h" + +#include +#include +#include + +const char* ffDetectInitSystem(FFInitSystemResult* result) { + // We only need to find the first user process, so 1024 entries should be enough + SYSTEM_PROCESS_INFORMATION buffer[1024] = {}; + ULONG size = sizeof(buffer); + NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size); + if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) { + return "NtQuerySystemInformation(SystemProcessInformation) failed"; + } + + for (SYSTEM_PROCESS_INFORMATION* ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) { + assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer)); + uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer); + if (ptr->InheritedFromUniqueProcessId == (HANDLE)(uintptr_t) 4 /* System */ && + len > 4 && _wcsnicmp(ptr->ImageName.Buffer + len - 4, L".exe", 4) == 0) { // smss.exe + result->pid = (uint32_t)(uintptr_t) ptr->UniqueProcessId; + // We have no permission to open the process for querying the full information + wchar_t exePath[MAX_PATH]; + _snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, ptr->ImageName.Buffer); + ffGetFileVersion(exePath, NULL, &result->version); + ffStrbufSetWS(&result->exe, exePath); + ffStrbufSetNWS(&result->name, len - 4, ptr->ImageName.Buffer); + return nullptr; + } + // The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated. + if (!ptr->NextEntryOffset) { + return "Could not find init system process"; + } + } +} diff --git a/src/detection/libc/libc_windows.cpp b/src/detection/libc/libc_windows.cpp index 20bbeda009..c3d866eb4d 100644 --- a/src/detection/libc/libc_windows.cpp +++ b/src/detection/libc/libc_windows.cpp @@ -7,48 +7,37 @@ extern "C" { #endif template -class version_t { - constexpr static auto buflen() noexcept { - unsigned int len = 2; // "." - if (Major == 0) { +struct version_t { + static constexpr uint32_t digits(uint32_t n) noexcept { + uint32_t len = 1; + for (auto temp = n / 10; temp; temp /= 10) { len++; - } else { - for (auto n = Major; n; len++, n /= 10); - } - - if (Minor == 0) { - len++; - } else { - for (auto n = Minor; n; len++, n /= 10); } return len; } - char buf[buflen()] = {}; + char buf[digits(Major) + digits(Minor) + 2]; - public: constexpr version_t() noexcept { - auto ptr = buf + buflen(); + auto ptr = buf + sizeof(buf); *--ptr = '\0'; - if (Minor == 0) { - *--ptr = '0'; - } else { - for (auto n = Minor; n; n /= 10) { - *--ptr = "0123456789"[n % 10]; + auto append = [&](uint32_t n) noexcept { + if (n == 0) { + *--ptr = '0'; + } else { + for (; n; n /= 10) { + *--ptr = static_cast('0' + n % 10); + } } - } + }; + + append(Minor); *--ptr = '.'; - if (Major == 0) { - *--ptr = '0'; - } else { - for (auto n = Major; n; n /= 10) { - *--ptr = "0123456789"[n % 10]; - } - } + append(Major); } - constexpr operator const char*() const { + constexpr operator const char*() const noexcept { return buf; } }; @@ -63,6 +52,6 @@ extern "C" const char* ffDetectLibc(FFLibcResult* result) { result->name = "msvcrt"; #endif - result->version = version<(__MSVCRT_VERSION__ >> 8), (__MSVCRT_VERSION__ & 8)>; + result->version = version<(__MSVCRT_VERSION__ >> 8), (__MSVCRT_VERSION__ & 0xFF)>; return nullptr; } diff --git a/src/detection/opengl/opengl_shared.c b/src/detection/opengl/opengl_shared.c index 2c6cf74f3f..b3fc2309bc 100644 --- a/src/detection/opengl/opengl_shared.c +++ b/src/detection/opengl/opengl_shared.c @@ -184,7 +184,7 @@ const char* ffOpenGLDetectByEGL(FFOpenGLResult* result) { } if (ffeglGetPlatformDisplay) { FF_DEBUG("Trying eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA)"); - display = ffeglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr); + display = ffeglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, nullptr /*EGL_DEFAULT_DISPLAY*/, nullptr); FF_DEBUG("eglGetPlatformDisplay() %s", display == EGL_NO_DISPLAY ? "failed" : "succeeded"); } else { FF_DEBUG("eglGetPlatformDisplay is unavailable, falling back to eglGetDisplay"); diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index a7b1ffa3f7..02dcb240ac 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -92,7 +92,8 @@ static bool parseOsRelease(const char* fileName, FFOSResult* result) { } // xdgConfigDirs contains plasma only - if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) { + // `ubuntustudio-desktop-core` is installed on both the full and core installations + if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop-core.list", FF_PATHTYPE_FILE)) { ffStrbufSetStatic(&result->name, "Ubuntu Studio"); ffStrbufSetStatic(&result->id, "ubuntu-studio"); ffStrbufSetStatic(&result->idLike, "ubuntu"); diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index 74c592035f..c0e8afb478 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -34,9 +34,8 @@ void ffPreparePublicIp(FFPublicIPOptions* options) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); if (pathStartIndex != host.length) { - ffStrbufAppendNS(&path, pathStartIndex, host.chars + (host.length - pathStartIndex)); - host.length = pathStartIndex; - host.chars[pathStartIndex] = '\0'; + ffStrbufAppendNS(&path, host.length - pathStartIndex, host.chars + pathStartIndex); + ffStrbufSubstrBefore(&host, pathStartIndex); } *status = ffNetworkingSendHttpRequest(state, host.chars, path.length == 0 ? "/" : path.chars, nullptr); diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c index e3b43a52a3..b7c2d0a5a4 100644 --- a/src/detection/terminalfont/terminalfont.c +++ b/src/detection/terminalfont/terminalfont.c @@ -3,7 +3,6 @@ #include "common/properties.h" #include "common/processing.h" #include "common/debug.h" -#include "common/strutil.h" #include "detection/terminalshell/terminalshell.h" static void detectAlacritty(FFTerminalFontResult* terminalFont) { @@ -19,6 +18,7 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { do { FFpropquery fontQueryToml[] = { { "normal =", &fontNormal }, + { "family =", &fontFamily }, { "size =", &fontSize }, }; @@ -35,6 +35,8 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { } while (false); if (fontNormal.length > 0) { + ffStrbufClear(&fontFamily); // If both `normal` and `family` are specified, `normal` takes precedence + // { family = "Fira Code", style = "Medium" } ffStrbufTrimSpace(&fontNormal); ffStrbufTrimRight(&fontNormal, '}'); @@ -67,14 +69,17 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle); } -static bool parseGhosttyConfig(FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize) { +static void parseGhosttyConfig(const FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize, FFlist* configFiles /* list of FFstrbuf */) { + // Maximum number of `config-file` directives to follow, guarding against runaway includes + enum { FF_GHOSTTY_MAX_CONFIG_FILES = 16 }; + FF_DEBUG("parsing config: %s", path->chars); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate(); if (!ffAppendFileBuffer(path->chars, &buffer)) { FF_DEBUG("cannot read config: %s", path->chars); - return false; + return; } char* line = nullptr; @@ -82,70 +87,94 @@ static bool parseGhosttyConfig(FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fon while (ffStrbufGetline(&line, &len, &buffer)) { if (ffParsePropLine(line, "font-family =", &temp)) { FF_DEBUG("found font-family='%s' in %s", temp.chars, path->chars); + // Latter overrides former; former becomes the fallback font if (fontName->length > 0) { ffStrbufDestroy(fontNameFallback); ffStrbufInitMove(fontNameFallback, fontName); } ffStrbufDestroy(fontName); ffStrbufInitMove(fontName, &temp); - } else if (ffParsePropLine(line, "font-size =", fontSize)) { + } else if (ffParsePropLine(line, "font-size =", &temp)) { FF_DEBUG("found font-size='%s' in %s", temp.chars, path->chars); // Latter overrides former ffStrbufDestroy(fontSize); ffStrbufInitMove(fontSize, &temp); + } else if (ffParsePropLine(line, "config-file =", &temp)) { + // Doc: https://ghostty.org/docs/config/reference#config-file + // A leading `?` suppresses errors if the file doesn't exist; missing files are skipped here either way + ffStrbufTrimLeft(&temp, '?'); + ffStrbufTrim(&temp, '"'); + if (temp.length == 0) { + continue; + } + + if (!ffStrbufStartsWithC(&temp, '/')) { + // Relative paths are relative to the file containing the `config-file` directive + FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy(path); + ffStrbufSubstrBeforeLastC(&absolutePath, '/'); + ffStrbufAppendC(&absolutePath, '/'); + ffStrbufAppend(&absolutePath, &temp); + ffStrbufDestroy(&temp); + ffStrbufInitMove(&temp, &absolutePath); + } + + // Each unique file is only loaded once, which also prevents include cycles + bool loaded = ffStrbufEqual(&temp, path); + if (!loaded) { + FF_LIST_FOR_EACH(FFstrbuf, it, *configFiles) { + if (ffStrbufEqual(it, &temp)) { + loaded = true; + break; + } + } + } + + if (loaded) { + FF_DEBUG("config-file '%s' was already loaded, skipping to avoid cycles", temp.chars); + } else if (configFiles->length >= FF_GHOSTTY_MAX_CONFIG_FILES) { + FF_DEBUG("too many config-file directives, ignoring '%s'", temp.chars); + } else { + FF_DEBUG("found config-file='%s' in %s", temp.chars, path->chars); + ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *configFiles), &temp); + } + ffStrbufClear(&temp); } } - return true; } -static void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont, const char* configPathMac, const char* configPathUnix) { +static void detectGhostty(FFTerminalFontResult* terminalFont, [[maybe_unused]] const char* configPathMac, const char* configPathUnix) { FF_DEBUG("detectGhostty: start"); FF_STRBUF_AUTO_DESTROY configPath = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontNameFallback = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); + FF_LIST_AUTO_DESTROY configFiles = ffListCreate(); // list of FFstrbuf + + // Ghostty loads the XDG config first, then (on macOS) the Application Support config, + // so values in the latter override the former + if (instance.state.platform.configDirs.length > 0) { + ffStrbufSet(&configPath, FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs)); + ffStrbufAppendS(&configPath, configPathUnix); // ghostty/config + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize, &configFiles); + } - if (configPathMac && configPathUnix) { #if __APPLE__ - ffStrbufSet(&configPath, &instance.state.platform.homeDir); - ffStrbufAppendS(&configPath, "Library/Application Support/"); - ffStrbufAppendS(&configPath, configPathMac); // com.mitchellh.ghostty/config - parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize); + ffStrbufSet(&configPath, &instance.state.platform.homeDir); + ffStrbufAppendS(&configPath, "Library/Application Support/"); + ffStrbufAppendS(&configPath, configPathMac); // com.mitchellh.ghostty/config + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize, &configFiles); #endif - if (instance.state.platform.configDirs.length > 0) { - ffStrbufSet(&configPath, FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs)); - ffStrbufAppendS(&configPath, configPathUnix); // ghostty/config - parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize); - } - } else { - // Try ghostty +show-config first - FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - const char* error = ffProcessAppendStdOut(&buffer, (char* const[]){ - exe->chars, - "+show-config", - nullptr, - }); - if (error == nullptr) { - char* line = nullptr; - size_t len = 0; - while (ffStrbufGetline(&line, &len, &buffer)) { - if (ffStrStartsWith(line, "font-family = ")) { - FF_DEBUG("found %s", line); - if (fontName.length > 0) { - ffStrbufDestroy(&fontNameFallback); - ffStrbufInitMove(&fontNameFallback, &fontName); - } - ffStrbufSetNS(&fontName, (uint32_t) (len - strlen("font-family = ")), line + strlen("font-family = ")); - } else if (ffStrStartsWith(line, "font-size = ")) { - FF_DEBUG("found %s", line); - // `ghostty +show-config` reports only one font size even if the config has multiple font sizes - ffStrbufSetNS(&fontSize, (uint32_t) (len - strlen("font-size = ")), line + strlen("font-size = ")); - } - } - } else { - FF_DEBUG("`ghostty +show-config` failed: %s", error); - } + // Files referenced by `config-file` don't take effect until the whole configuration is loaded, + // so they are parsed after all root config files, in the order they were found + for (uint32_t i = 0; i < configFiles.length; ++i) { + // Copy the path as parseGhosttyConfig may grow the list, invalidating pointers into it + ffStrbufSet(&configPath, FF_LIST_GET(FFstrbuf, configFiles, i)); + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize, &configFiles); + } + + FF_LIST_FOR_EACH(FFstrbuf, it, configFiles) { + ffStrbufDestroy(it); } if (fontName.length == 0) { @@ -365,9 +394,9 @@ static bool detectTerminalFontCommon(const FFTerminalResult* terminal, FFTermina } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "contour")) { detectContour(&terminal->exe, terminalFont); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "ghostty")) { - detectGhostty(&terminal->exe, terminalFont, nullptr, nullptr); + detectGhostty(terminalFont, "com.mitchellh.ghostty/config", "ghostty/config"); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "Muxy")) { - detectGhostty(&terminal->exe, terminalFont, "Muxy/ghostty.conf", "muxy/ghostty.conf"); + detectGhostty(terminalFont, "Muxy/ghostty.conf", "muxy/ghostty.conf"); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "rio")) { detectRio(terminalFont); } diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index 97cb9c3ef3..fa323aa9eb 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -90,7 +90,13 @@ static bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version) { return false; } uint32_t index = ffStrbufFirstIndexC(version, ' '); // skip "fish," - index = ffStrbufNextIndexC(version, index + 1, ' '); // skip "version" + while (index + 1 < version->length && !ffCharIsDigit(version->chars[index + 1])) { + index = ffStrbufNextIndexC(version, index + 1, ' '); // skip "version" + } + if (index + 1 >= version->length) { + return false; + } + ffStrbufSubstrAfter(version, index); ffStrbufSubstrBeforeFirstC(version, ' '); return true; diff --git a/src/detection/theme/theme_windows.c b/src/detection/theme/theme_windows.c index 68372eb0c4..4c82d2c04e 100644 --- a/src/detection/theme/theme_windows.c +++ b/src/detection/theme/theme_windows.c @@ -1,27 +1,34 @@ #include "theme.h" #include "detection/os/os.h" +#include "detection/displayserver/displayserver.h" const char* ffDetectTheme(FFThemeResult* result) { const FFOSResult* os = ffDetectOS(); + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + if (ffStrbufEqualS(&displayServer->wmProcessName, "dwm.exe")) { + uint32_t ver = (uint32_t) ffStrbufToUInt(&os->version, 0); - if (ver > 1000) { - // Windows Server - if (ver >= 2016) { - ffStrbufSetStatic(&result->theme1, "Fluent"); - } else if (ver >= 2012) { - ffStrbufSetStatic(&result->theme1, "Metro"); - } else { - ffStrbufSetStatic(&result->theme1, "Aero"); - } - } else { - if (ver >= 10) { - ffStrbufSetStatic(&result->theme1, "Fluent"); - } else if (ver >= 8) { - ffStrbufSetStatic(&result->theme1, "Metro"); + if (ver > 1000) { + // Windows Server + if (ver >= 2016) { + ffStrbufSetStatic(&result->theme1, "Fluent"); + } else if (ver >= 2012) { + ffStrbufSetStatic(&result->theme1, "Metro"); + } else { + ffStrbufSetStatic(&result->theme1, "Aero"); + } } else { - ffStrbufSetStatic(&result->theme1, "Aero"); + if (ver >= 10) { + ffStrbufSetStatic(&result->theme1, "Fluent"); + } else if (ver >= 8) { + ffStrbufSetStatic(&result->theme1, "Metro"); + } else { + ffStrbufSetStatic(&result->theme1, "Aero"); + } } + } else if (ffStrbufEqualS(&displayServer->wmProcessName, "explorer.exe")) { + ffStrbufSetStatic(&result->theme1, "Basic"); } return nullptr; } diff --git a/src/fastfetch.c b/src/fastfetch.c index cad134e15c..b6c2ea2018 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -3,6 +3,7 @@ #include "detection/version/version.h" #include "logo/logo.h" #include "common/commandoption.h" +#include "common/genconfig.h" #include "common/init.h" #include "common/io.h" #include "common/jsonconfig.h" @@ -15,6 +16,13 @@ #include #include +#ifndef _WIN32 + #include +#else + #include + #include "common/windows/nt.h" +#endif + [[gnu::cold]] static void printCommandFormatHelpJson(void) { yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr); @@ -432,12 +440,7 @@ static bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg) } [[gnu::cold]] -static void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig) { - if (data->resultDoc) { - fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); - exit(477); - } - +static void setupGenConfigPath(FFdata* data, const char* filePath) { if (!filePath) { if (instance.state.platform.configDirs.length == 0) { fprintf(stderr, "Error: No config directory found to generate config file in. Use --gen-config to specify a path\n"); @@ -451,13 +454,23 @@ static void generateConfigFile(FFdata* data, bool force, const char* filePath, b } else { ffStrbufSetS(&data->genConfigPath, filePath); } +} - if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) { - fprintf(stderr, "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", data->genConfigPath.chars, fullConfig ? "-full" : ""); +[[gnu::cold]] +static void generateConfigFile(FFdata* data, const char* filePath) { + if (data->resultDoc) { + fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); exit(477); } - data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; + setupGenConfigPath(data, filePath); + + if (ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) { + fprintf(stderr, "Error: file `%s` exists. Please remove it before generating a new one\n", data->genConfigPath.chars); + exit(477); + } + + data->docType = FF_RESULT_DOC_TYPE_CONFIG; data->resultDoc = yyjson_mut_doc_new(nullptr); } @@ -586,6 +599,19 @@ static void enableJsonOutput(FFdata* data) { yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc)); } +static void genConfigCommon(FFdata* data, const char* value) { + if (!getenv("NO_COLOR") && isatty(STDOUT_FILENO) && isatty(STDIN_FILENO) + #ifdef _WIN32 + && ffIsWindows10OrGreater() + #endif + ) { + data->genConfigInteractive = true; + setupGenConfigPath(data, value); + } else { + generateConfigFile(data, value); + } +} + static void parseCommand(FFdata* data, char* key, char* value) { if (ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help")) { printCommandHelp(value); @@ -647,13 +673,7 @@ static void parseCommand(FFdata* data, char* key, char* value) { exit(0); } else if (ffStrEqualsIgnCase(key, "--gen-config")) { - generateConfigFile(data, false, value, false); - } else if (ffStrEqualsIgnCase(key, "--gen-config-force")) { - generateConfigFile(data, true, value, false); - } else if (ffStrEqualsIgnCase(key, "--gen-config-full")) { - generateConfigFile(data, false, value, true); - } else if (ffStrEqualsIgnCase(key, "--gen-config-full-force")) { - generateConfigFile(data, true, value, true); + genConfigCommon(data, value); } else if (ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config")) { optionParseConfigFile(data, key, value); } else if (ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json")) { @@ -670,6 +690,12 @@ static void parseCommand(FFdata* data, char* key, char* value) { } } else if (ffStrEqualsIgnCase(key, "--dynamic-interval")) { instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds + } else if (ffStrEqualsIgnCase(key, "-w") || ffStrEqualsIgnCase(key, "--watch")) { + if (value == nullptr) { + instance.state.dynamicInterval = 1000; // default to 1 second if no value is provided + } else { + instance.state.dynamicInterval = ffOptionParseUInt32(key, value) * 1000; // seconds to milliseconds + } } else { return; } @@ -822,6 +848,14 @@ static void writeConfigFile(FFdata* data) { ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo); ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display); ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general); + } else if (data->genConfigInteractive) { + if (instance.config.logo.type == FF_LOGO_TYPE_NONE) { + yyjson_mut_obj_add_null(doc, root, "logo"); + } else if (instance.config.logo.type == FF_LOGO_TYPE_SMALL) { + yyjson_mut_val* logo = yyjson_mut_obj(doc); + yyjson_mut_obj_add_str(doc, logo, "type", "small"); + yyjson_mut_obj_add_val(doc, root, "logo", logo); + } } ffMigrateCommandOptionToJsonc(data); @@ -871,6 +905,18 @@ int main(int argc, char** argv) { if (__builtin_expect(data.genConfigPath.length == 0, true)) { run(&data); } else { + // If we don't have a custom structure, use the default one + if (data.structure.length == 0) { + ffStrbufSetS(&data.structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string + } + if (data.genConfigInteractive && !ffGenConfigInteractive(&data)) { + // User cancelled the interactive config generation + ffStrbufDestroy(&data.structure); + ffStrbufDestroy(&data.structureDisabled); + yyjson_doc_free(data.configDoc); + ffStrbufDestroy(&data.genConfigPath); + return 0; + } writeConfigFile(&data); } diff --git a/src/logo/ascii/e.inc b/src/logo/ascii/e.inc index 3e5fd91081..11e3672d8c 100644 --- a/src/logo/ascii/e.inc +++ b/src/logo/ascii/e.inc @@ -65,17 +65,6 @@ static const FFlogo E[] = { }, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_EMPEROROS - // EmperorOS - { - .names = { "Emperor" }, - .lines = FASTFETCH_DATATEXT_LOGO_EMPEROROS, - .colors = { - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_DEFAULT, - }, - }, - #endif #ifdef FASTFETCH_DATATEXT_LOGO_ENOS // EN-OS { diff --git a/src/logo/ascii/e/emperoros.txt b/src/logo/ascii/e/emperoros.txt deleted file mode 100644 index 55dcaa97c7..0000000000 --- a/src/logo/ascii/e/emperoros.txt +++ /dev/null @@ -1,13 +0,0 @@ - !! - !!!! - llllll - llllll - IIIIIIIIII - IIIIIIIIIIIIIIIIIIII -;;;;;;;;;;;;;;;;;;;;;;;; - ;;;;;;;;;;;;;;;;;;;; - :;;::;:;:: - :::::: - ,,,,,, - ,,,, - "" \ No newline at end of file diff --git a/src/logo/ascii/f.inc b/src/logo/ascii/f.inc index eaa0ae0366..921d88cb9f 100644 --- a/src/logo/ascii/f.inc +++ b/src/logo/ascii/f.inc @@ -283,19 +283,6 @@ static const FFlogo F[] = { .colorTitle = FF_COLOR_FG_DEFAULT, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_FURRETO - // Furreto - { - .names = { "Furreto" }, - .lines = FASTFETCH_DATATEXT_LOGO_FURRETO, - .colors = { - FF_COLOR_FG_WHITE, - FF_COLOR_FG_LIGHT_MAGENTA, - }, - .colorKeys = FF_COLOR_FG_CYAN, - .colorTitle = FF_COLOR_FG_CYAN, - }, - #endif // LAST {}, }; diff --git a/src/logo/ascii/f/furreto.txt b/src/logo/ascii/f/furreto.txt deleted file mode 100644 index 95ee07c989..0000000000 --- a/src/logo/ascii/f/furreto.txt +++ /dev/null @@ -1,22 +0,0 @@ - .$2xOOko $1.$2odd, - oX$1WW$2KOOOO. 'ON$1WW$20kkk. - $1.$2k0XKOOOOOOcOON$1W$2NOOOOO. - xOOOOOOOOOkkOOOOOOOOO; - $1.$2O0OkkocxO000000kcdk0OO0OOkx - k$1W$1M$2Xkkkkloxkkkx; :dxxxxddc... - 'kO0OOOOOkc .cl:..kk0KK0Okc - ;kOOO0000xd. dO00000Oo .xkO$1NMM$2XOOOO -.dddxkOOOkddc.kKN$1WW$2N000000l.ddk0000OOOO. - 'dd:;ddddd;.dK$1MMMW$2K00KKK0O::ddxkO00Oko - .okxkOKK0kkOO00KKOxxlodddddddl - .00OOkkkkkkkkOOO00OOOO0O; .dddl - 'kkkkkxxkkkkkkkOOkxdxkxxddd. - cddddddddxxkkkkkxddddddddddo - 'ddddddodddddddddddddddddddc - $1.$2ddddddodddddddddodddddddc - .odddo. - - $1.$2kOOkkk; - lkK$1WN$2kkkxc - kkxkkkkkkx. - ,,..xxx. \ No newline at end of file diff --git a/src/logo/ascii/h.inc b/src/logo/ascii/h.inc index 9d471eb737..d4efd6cc47 100644 --- a/src/logo/ascii/h.inc +++ b/src/logo/ascii/h.inc @@ -168,19 +168,6 @@ static const FFlogo H[] = { .colorTitle = FF_COLOR_FG_GREEN, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_HYPROS - // HyprOS - { - .names = { "hypros" }, - .lines = FASTFETCH_DATATEXT_LOGO_HYPROS, - .colors = { - FF_COLOR_FG_RED, - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_CYAN, - FF_COLOR_FG_BLUE, - }, - }, - #endif #ifdef FASTFETCH_DATATEXT_LOGO_HYPERBOLA // Hyperbola { diff --git a/src/logo/ascii/h/hypros.txt b/src/logo/ascii/h/hypros.txt deleted file mode 100644 index 5b7d4dddf0..0000000000 --- a/src/logo/ascii/h/hypros.txt +++ /dev/null @@ -1,17 +0,0 @@ - ___ - ,adZZEEEE#&$2>=x. - $1,zAP*~'$4_,-$2'~*VM$2N&x. - $1,%&P^`$4<$3,.$4<<$2,-===--.N>x - $1.%M7$4//$3,%^$2,x<3#$13EEbo$2<&>&b - $1&#/$4/$3.<^$4/$2x<>^$4-.`$1`+&WW$2<&N&; -$1/#/$4//$34$4//$2/W/ $4^+.`$1`###$2NM\ -$1##'$4|$3.l$4|$2,&/ $4`.',$1I#I$2HI# -$1#I$4||$3`I$4|$2(#( $3)`'$1)##$2H~^ -$1@\$4|||$3\$4\$2`X\ $3///$1,##%V$3'/ -$4\\\\\\$3Y,$2*@b, $3.-+//$1/&#%#/$3,' -$4`\\\\$2,.$4\$3<$2`*$3^`x<$1,z<#&#x"$3,' - $3`x<<$2`Xx,$3`<_`$1+{##&@P^$4'>$3' - $3`<_<<$2^<\-.$3`*`>$1<^'$4,-' - $3`<_=-$2^\Xx$1XX\.$3+<. - $3`^<_-$2^timeRemaining, &timeStr); } - FF_STRBUF_AUTO_DESTROY statusStr = ffStrbufCreate(); + FF_LIST_AUTO_DESTROY status = ffListCreate(); if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) { - ffStrbufAppendS(&statusStr, "AC Connected, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "AC Connected"); } if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) { - ffStrbufAppendS(&statusStr, "USB Connected, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "USB Connected"); } if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { - ffStrbufAppendS(&statusStr, "Wireless Connected, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Wireless Connected"); } if (result->status & FF_BATTERY_STATUS_CHARGING) { - ffStrbufAppendS(&statusStr, "Charging, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Charging"); } if (result->status & FF_BATTERY_STATUS_DISCHARGING) { - ffStrbufAppendS(&statusStr, "Discharging, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Discharging"); } if (result->status & FF_BATTERY_STATUS_CRITICAL) { - ffStrbufAppendS(&statusStr, "Critical, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Critical"); } if (result->status & FF_BATTERY_STATUS_UNKNOWN) { - ffStrbufAppendS(&statusStr, "Unknown, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Unknown"); } - ffStrbufSubstrBefore(&statusStr, statusStr.length - 2); // Remove last ", " FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(result->manufacturer, "manufacturer"), FF_ARG(result->modelName, "model-name"), FF_ARG(result->technology, "technology"), FF_ARG(capacityNum, "capacity"), - FF_ARG(statusStr, "status"), + FF_ARG(status, "status"), FF_ARG(tempStr, "temperature"), FF_ARG(result->cycleCount, "cycle-count"), FF_ARG(result->serial, "serial"), @@ -322,5 +321,6 @@ FFModuleBaseInfo ffBatteryModuleInfo = { { "Battery time remaining minutes", "time-minutes" }, { "Battery time remaining seconds", "time-seconds" }, { "Battery time remaining (formatted)", "time-formatted" }, - })) + })), + .defaultOrder = 44, }; diff --git a/src/modules/bios/bios.c b/src/modules/bios/bios.c index 6113b09296..c000080f49 100644 --- a/src/modules/bios/bios.c +++ b/src/modules/bios/bios.c @@ -144,5 +144,6 @@ FFModuleBaseInfo ffBiosModuleInfo = { { "BIOS vendor", "vendor" }, { "BIOS version", "version" }, { "Firmware type", "type" }, - })) + })), + .defaultOrder = 5, }; diff --git a/src/modules/bluetooth/bluetooth.c b/src/modules/bluetooth/bluetooth.c index 6721c336da..42a251886a 100644 --- a/src/modules/bluetooth/bluetooth.c +++ b/src/modules/bluetooth/bluetooth.c @@ -166,5 +166,6 @@ FFModuleBaseInfo ffBluetoothModuleInfo = { { "Battery percentage number", "battery-percentage" }, { "Is connected", "connected" }, { "Battery percentage bar", "battery-percentage-bar" }, - })) + })), + .defaultOrder = 58, }; diff --git a/src/modules/bluetoothradio/bluetoothradio.c b/src/modules/bluetoothradio/bluetoothradio.c index 9efc0a0578..fc113fc832 100644 --- a/src/modules/bluetoothradio/bluetoothradio.c +++ b/src/modules/bluetoothradio/bluetoothradio.c @@ -207,5 +207,6 @@ FFModuleBaseInfo ffBluetoothRadioModuleInfo = { { "Vendor", "vendor" }, { "Discoverable", "discoverable" }, { "Connectable / Pairable", "connectable" }, - })) + })), + .defaultOrder = 59, }; diff --git a/src/modules/board/board.c b/src/modules/board/board.c index bc8f37caed..1a85ea3930 100644 --- a/src/modules/board/board.c +++ b/src/modules/board/board.c @@ -121,5 +121,6 @@ FFModuleBaseInfo ffBoardModuleInfo = { { "Board vendor", "vendor" }, { "Board version", "version" }, { "Board serial number", "serial" }, - })) + })), + .defaultOrder = 7, }; diff --git a/src/modules/bootmgr/bootmgr.c b/src/modules/bootmgr/bootmgr.c index 05cb46713a..494885fd6c 100644 --- a/src/modules/bootmgr/bootmgr.c +++ b/src/modules/bootmgr/bootmgr.c @@ -117,5 +117,6 @@ FFModuleBaseInfo ffBootmgrModuleInfo = { { "Firmware file name", "firmware-name" }, { "Is secure boot enabled", "secure-boot" }, { "Boot order", "order" }, - })) + })), + .defaultOrder = 6, }; diff --git a/src/modules/break/break.c b/src/modules/break/break.c index 7bca2b61ca..011458a283 100644 --- a/src/modules/break/break.c +++ b/src/modules/break/break.c @@ -33,4 +33,5 @@ FFModuleBaseInfo ffBreakModuleInfo = { .destroyOptions = (void*) ffDestroyBreakOptions, .parseJsonObject = (void*) ffParseBreakJsonObject, .printModule = (void*) ffPrintBreak, + .defaultOrder = 71, }; diff --git a/src/modules/brightness/brightness.c b/src/modules/brightness/brightness.c index 1d4971fcb6..479a2ccb9b 100644 --- a/src/modules/brightness/brightness.c +++ b/src/modules/brightness/brightness.c @@ -206,5 +206,6 @@ FFModuleBaseInfo ffBrightnessModuleInfo = { { "Current brightness value", "current" }, { "Screen brightness (percentage bar)", "percentage-bar" }, { "Is built-in screen", "is-builtin" }, - })) + })), + .defaultOrder = 18, }; diff --git a/src/modules/btrfs/btrfs.c b/src/modules/btrfs/btrfs.c index d9661c2e30..21068e85db 100644 --- a/src/modules/btrfs/btrfs.c +++ b/src/modules/btrfs/btrfs.c @@ -220,5 +220,6 @@ FFModuleBaseInfo ffBtrfsModuleInfo = { { "Allocated percentage bar", "allocated-percentage-bar" }, { "Node size", "node-size" }, { "Sector size", "sector-size" }, - })) + })), + .defaultOrder = 42, }; diff --git a/src/modules/camera/camera.c b/src/modules/camera/camera.c index 08cb107067..02c454bbbf 100644 --- a/src/modules/camera/camera.c +++ b/src/modules/camera/camera.c @@ -1,7 +1,5 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/strutil.h" -#include "detection/libc/libc.h" #include "detection/camera/camera.h" #include "modules/camera/camera.h" @@ -53,6 +51,7 @@ bool ffPrintCamera(FFCameraOptions* options) { FF_LIST_FOR_EACH (FFCameraResult, dev, result) { ffStrbufDestroy(&dev->name); + ffStrbufDestroy(&dev->vendor); ffStrbufDestroy(&dev->id); ffStrbufDestroy(&dev->colorspace); } @@ -99,6 +98,7 @@ bool ffGenerateCameraJsonResult([[maybe_unused]] FFCameraOptions* options, yyjso FF_LIST_FOR_EACH (FFCameraResult, dev, result) { ffStrbufDestroy(&dev->name); + ffStrbufDestroy(&dev->vendor); ffStrbufDestroy(&dev->id); ffStrbufDestroy(&dev->colorspace); } @@ -130,5 +130,6 @@ FFModuleBaseInfo ffCameraModuleInfo = { { "Identifier", "id" }, { "Width (in px)", "width" }, { "Height (in px)", "height" }, - })) + })), + .defaultOrder = 61, }; diff --git a/src/modules/chassis/chassis.c b/src/modules/chassis/chassis.c index 1cd3089098..9b5b21e7a6 100644 --- a/src/modules/chassis/chassis.c +++ b/src/modules/chassis/chassis.c @@ -124,4 +124,5 @@ FFModuleBaseInfo ffChassisModuleInfo = { { "Chassis version", "version" }, { "Chassis serial number", "serial" }, })), + .defaultOrder = 8, }; diff --git a/src/modules/codec/codec.c b/src/modules/codec/codec.c index 5d3cbac902..a9a465bf84 100644 --- a/src/modules/codec/codec.c +++ b/src/modules/codec/codec.c @@ -264,5 +264,6 @@ FFModuleBaseInfo ffCodecModuleInfo = { { "Decoder / Encoder", "direction" }, { "Compatibility alias of codec types", "types" }, { "Platform API used for detection", "platform-api" }, - })) + })), + .defaultOrder = 37, }; diff --git a/src/modules/colors/colors.c b/src/modules/colors/colors.c index 43cdffb31b..8d225d4bf1 100644 --- a/src/modules/colors/colors.c +++ b/src/modules/colors/colors.c @@ -302,4 +302,5 @@ FFModuleBaseInfo ffColorsModuleInfo = { .parseJsonObject = (void*) ffParseColorsJsonObject, .printModule = (void*) ffPrintColors, .generateJsonConfig = (void*) ffGenerateColorsJsonConfig, + .defaultOrder = 72, }; diff --git a/src/modules/command/command.c b/src/modules/command/command.c index 751b971398..4de947a792 100644 --- a/src/modules/command/command.c +++ b/src/modules/command/command.c @@ -163,5 +163,5 @@ FFModuleBaseInfo ffCommandModuleInfo = { .generateJsonConfig = (void*) ffGenerateCommandJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Command result", "result" }, - })) + })), }; diff --git a/src/modules/cpu/cpu.c b/src/modules/cpu/cpu.c index 51f84c9f88..2716b2e037 100644 --- a/src/modules/cpu/cpu.c +++ b/src/modules/cpu/cpu.c @@ -276,5 +276,6 @@ FFModuleBaseInfo ffCPUModuleInfo = { { "CPU code name", "code-name" }, { "CPU technology", "technology" }, #endif - })) + })), + .defaultOrder = 33, }; diff --git a/src/modules/cpucache/cpucache.c b/src/modules/cpucache/cpucache.c index 9753e41275..7dd39d1cb6 100644 --- a/src/modules/cpucache/cpucache.c +++ b/src/modules/cpucache/cpucache.c @@ -234,5 +234,6 @@ FFModuleBaseInfo ffCPUCacheModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Separate result", "result" }, { "Sum result", "sum" }, - })) + })), + .defaultOrder = 34, }; diff --git a/src/modules/cpuusage/cpuusage.c b/src/modules/cpuusage/cpuusage.c index 5c01e914ac..ea592a2739 100644 --- a/src/modules/cpuusage/cpuusage.c +++ b/src/modules/cpuusage/cpuusage.c @@ -198,5 +198,6 @@ FFModuleBaseInfo ffCPUUsageModuleInfo = { { "CPU usage (percentage bar, average)", "avg-bar" }, { "CPU usage (percentage bar, maximum)", "max-bar" }, { "CPU usage (percentage bar, minimum)", "min-bar" }, - })) + })), + .defaultOrder = 35, }; diff --git a/src/modules/cursor/cursor.c b/src/modules/cursor/cursor.c index 748f78cc7e..1be3ac6e35 100644 --- a/src/modules/cursor/cursor.c +++ b/src/modules/cursor/cursor.c @@ -112,4 +112,5 @@ FFModuleBaseInfo ffCursorModuleInfo = { { "Cursor theme", "theme" }, { "Cursor size", "size" }, })), + .defaultOrder = 27, }; diff --git a/src/modules/datetime/datetime.c b/src/modules/datetime/datetime.c index 8e47e147c0..14e3b8251f 100644 --- a/src/modules/datetime/datetime.c +++ b/src/modules/datetime/datetime.c @@ -185,5 +185,6 @@ FFModuleBaseInfo ffDateTimeModuleInfo = { { "Locale-dependent timezone name or abbreviation", "timezone-name" }, { "Day in month with leading zero", "day-pretty" }, { "AM or PM", "am-pm" }, - })) + })), + .defaultOrder = 52, }; diff --git a/src/modules/de/de.c b/src/modules/de/de.c index ad714d6f52..8e301fa32e 100644 --- a/src/modules/de/de.c +++ b/src/modules/de/de.c @@ -94,5 +94,6 @@ FFModuleBaseInfo ffDEModuleInfo = { { "DE process name", "process-name" }, { "DE pretty name", "pretty-name" }, { "DE version", "version" }, - })) + })), + .defaultOrder = 21, }; diff --git a/src/modules/disk/disk.c b/src/modules/disk/disk.c index 72192e260e..74b6488558 100644 --- a/src/modules/disk/disk.c +++ b/src/modules/disk/disk.c @@ -486,5 +486,6 @@ FFModuleBaseInfo ffDiskModuleInfo = { { "Years fraction after creation", "years-fraction" }, { "Size free", "size-free" }, { "Size available", "size-available" }, - })) + })), + .defaultOrder = 41, }; diff --git a/src/modules/diskio/diskio.c b/src/modules/diskio/diskio.c index 3ff31fad4f..372910d10a 100644 --- a/src/modules/diskio/diskio.c +++ b/src/modules/diskio/diskio.c @@ -189,5 +189,6 @@ FFModuleBaseInfo ffDiskIOModuleInfo = { { "Size of data written [per second] (in bytes)", "bytes-written" }, { "Number of reads", "read-count" }, { "Number of writes", "write-count" }, - })) + })), + .defaultOrder = 67, }; diff --git a/src/modules/display/display.c b/src/modules/display/display.c index bdcec58eac..eceb9a2d13 100644 --- a/src/modules/display/display.c +++ b/src/modules/display/display.c @@ -445,5 +445,6 @@ FFModuleBaseInfo ffDisplayModuleInfo = { { "Screen preferred height (in pixels)", "preferred-height" }, { "Screen preferred refresh rate (in Hz)", "preferred-refresh-rate" }, { "DPI", "dpi" }, - })) + })), + .defaultOrder = 17, }; diff --git a/src/modules/dns/dns.c b/src/modules/dns/dns.c index 79df55bacb..396c3bd431 100644 --- a/src/modules/dns/dns.c +++ b/src/modules/dns/dns.c @@ -144,5 +144,6 @@ FFModuleBaseInfo ffDNSModuleInfo = { .generateJsonConfig = (void*) ffGenerateDNSJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "DNS result", "result" }, - })) + })), + .defaultOrder = 50, }; diff --git a/src/modules/editor/editor.c b/src/modules/editor/editor.c index 352661ecbb..342b31812c 100644 --- a/src/modules/editor/editor.c +++ b/src/modules/editor/editor.c @@ -117,5 +117,6 @@ FFModuleBaseInfo ffEditorModuleInfo = { { "Exe name of real path", "exe-name" }, { "Full path of real path", "path" }, { "Version", "version" }, - })) + })), + .defaultOrder = 16, }; diff --git a/src/modules/font/font.c b/src/modules/font/font.c index 14cfa8fb18..be940b32d1 100644 --- a/src/modules/font/font.c +++ b/src/modules/font/font.c @@ -109,5 +109,6 @@ FFModuleBaseInfo ffFontModuleInfo = { { "Font 3", "font3" }, { "Font 4", "font4" }, { "Combined fonts for display", "combined" }, - })) + })), + .defaultOrder = 26, }; diff --git a/src/modules/gamepad/gamepad.c b/src/modules/gamepad/gamepad.c index 5404cb8c26..f3c3bdfe40 100644 --- a/src/modules/gamepad/gamepad.c +++ b/src/modules/gamepad/gamepad.c @@ -202,5 +202,6 @@ FFModuleBaseInfo ffGamepadModuleInfo = { { "Serial number", "serial" }, { "Battery percentage num", "battery-percentage" }, { "Battery percentage bar", "battery-percentage-bar" }, - })) + })), + .defaultOrder = 62, }; diff --git a/src/modules/gpu/gpu.c b/src/modules/gpu/gpu.c index 163fca8fb5..19ba8c868b 100644 --- a/src/modules/gpu/gpu.c +++ b/src/modules/gpu/gpu.c @@ -511,4 +511,5 @@ FFModuleBaseInfo ffGPUModuleInfo = { { "PCIe maximum speed in gen and lanes", "pcie-max-speed" }, { "PCIe current speed in gen and lanes", "pcie-curr-speed" }, })), + .defaultOrder = 36, }; diff --git a/src/modules/host/host.c b/src/modules/host/host.c index 680ba8849d..041ab712e6 100644 --- a/src/modules/host/host.c +++ b/src/modules/host/host.c @@ -152,5 +152,6 @@ FFModuleBaseInfo ffHostModuleInfo = { { "Product vendor", "vendor" }, { "Product serial number", "serial" }, { "Product uuid", "uuid" }, - })) + })), + .defaultOrder = 4, }; diff --git a/src/modules/icons/icons.c b/src/modules/icons/icons.c index d52ddc24b3..15dea09287 100644 --- a/src/modules/icons/icons.c +++ b/src/modules/icons/icons.c @@ -105,5 +105,6 @@ FFModuleBaseInfo ffIconsModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Icons part 1", "icons1" }, { "Icons part 2", "icons2" }, - })) + })), + .defaultOrder = 25, }; diff --git a/src/modules/initsystem/initsystem.c b/src/modules/initsystem/initsystem.c index faab272a31..7a02e0309b 100644 --- a/src/modules/initsystem/initsystem.c +++ b/src/modules/initsystem/initsystem.c @@ -116,5 +116,6 @@ FFModuleBaseInfo ffInitSystemModuleInfo = { { "Init system exe path", "exe" }, { "Init system version path", "version" }, { "Init system pid", "pid" }, - })) + })), + .defaultOrder = 10, }; diff --git a/src/modules/kernel/kernel.c b/src/modules/kernel/kernel.c index 0c40d39f6a..9ae5878df4 100644 --- a/src/modules/kernel/kernel.c +++ b/src/modules/kernel/kernel.c @@ -77,5 +77,6 @@ FFModuleBaseInfo ffKernelModuleInfo = { { "Architecture", "arch" }, { "Display version", "display-version" }, { "Page size", "page-size" }, - })) + })), + .defaultOrder = 9, }; diff --git a/src/modules/keyboard/keyboard.c b/src/modules/keyboard/keyboard.c index 5e3f5cbfe2..76e6c36f95 100644 --- a/src/modules/keyboard/keyboard.c +++ b/src/modules/keyboard/keyboard.c @@ -160,5 +160,6 @@ FFModuleBaseInfo ffKeyboardModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Name", "name" }, { "Serial number", "serial" }, - })) + })), + .defaultOrder = 64, }; diff --git a/src/modules/lm/lm.c b/src/modules/lm/lm.c index b8e7c1fc39..ed945bad46 100644 --- a/src/modules/lm/lm.c +++ b/src/modules/lm/lm.c @@ -118,5 +118,6 @@ FFModuleBaseInfo ffLMModuleInfo = { { "LM service", "service" }, { "LM type", "type" }, { "LM version", "version" }, - })) + })), + .defaultOrder = 20, }; diff --git a/src/modules/loadavg/loadavg.c b/src/modules/loadavg/loadavg.c index 61939f5151..a6fb6f800a 100644 --- a/src/modules/loadavg/loadavg.c +++ b/src/modules/loadavg/loadavg.c @@ -160,5 +160,6 @@ FFModuleBaseInfo ffLoadavgModuleInfo = { { "Load average over 1min", "loadavg1" }, { "Load average over 5min", "loadavg2" }, { "Load average over 15min", "loadavg3" }, - })) + })), + .defaultOrder = 12, }; diff --git a/src/modules/locale/locale.c b/src/modules/locale/locale.c index 9c25341aa2..cb9c8e7ec5 100644 --- a/src/modules/locale/locale.c +++ b/src/modules/locale/locale.c @@ -77,5 +77,6 @@ FFModuleBaseInfo ffLocaleModuleInfo = { .generateJsonConfig = (void*) ffGenerateLocaleJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Locale code", "result" }, - })) + })), + .defaultOrder = 53, }; diff --git a/src/modules/localip/localip.c b/src/modules/localip/localip.c index 952d72ef66..eeb304e283 100644 --- a/src/modules/localip/localip.c +++ b/src/modules/localip/localip.c @@ -439,5 +439,6 @@ FFModuleBaseInfo ffLocalIPModuleInfo = { { "MTU size in bytes", "mtu" }, { "Link speed (formatted)", "speed" }, { "Interface flags", "flags" }, - })) + })), + .defaultOrder = 49, }; diff --git a/src/modules/media/media.c b/src/modules/media/media.c index 884d8e4521..5f926d1156 100644 --- a/src/modules/media/media.c +++ b/src/modules/media/media.c @@ -273,5 +273,6 @@ FFModuleBaseInfo ffMediaModuleInfo = { { "Player name", "player-name" }, { "Player ID", "player-id" }, { "URL", "url" }, - })) + })), + .defaultOrder = 47, }; diff --git a/src/modules/memory/memory.c b/src/modules/memory/memory.c index efdf656437..73a8fc173b 100644 --- a/src/modules/memory/memory.c +++ b/src/modules/memory/memory.c @@ -131,5 +131,6 @@ FFModuleBaseInfo ffMemoryModuleInfo = { { "Total size", "total" }, { "Percentage used (num)", "percentage" }, { "Percentage used (bar)", "percentage-bar" }, - })) + })), + .defaultOrder = 38, }; diff --git a/src/modules/monitor/monitor.c b/src/modules/monitor/monitor.c index 41beeaba2b..6c8bed1708 100644 --- a/src/modules/monitor/monitor.c +++ b/src/modules/monitor/monitor.c @@ -125,5 +125,6 @@ FFModuleBaseInfo ffMonitorModuleInfo = { { "Serial number", "serial" }, { "Maximum refresh rate in Hz", "refresh-rate" }, { "True if the display is HDR compatible", "hdr-compatible" }, - })) + })), + .defaultOrder = 19, }; diff --git a/src/modules/mouse/mouse.c b/src/modules/mouse/mouse.c index 37e78be9ae..82dd6b053e 100644 --- a/src/modules/mouse/mouse.c +++ b/src/modules/mouse/mouse.c @@ -163,5 +163,6 @@ FFModuleBaseInfo ffMouseModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Mouse name", "name" }, { "Mouse serial number", "serial" }, - })) + })), + .defaultOrder = 63, }; diff --git a/src/modules/netio/netio.c b/src/modules/netio/netio.c index 17047fffd9..8607a21af6 100644 --- a/src/modules/netio/netio.c +++ b/src/modules/netio/netio.c @@ -220,5 +220,6 @@ FFModuleBaseInfo ffNetIOModuleInfo = { { "Number of errors sent [per second]", "tx-errors" }, { "Number of packets dropped when receiving [per second]", "rx-drops" }, { "Number of packets dropped when sending [per second]", "tx-drops" }, - })) + })), + .defaultOrder = 66, }; diff --git a/src/modules/opencl/opencl.c b/src/modules/opencl/opencl.c index acfe861cb9..0a80d9d16b 100644 --- a/src/modules/opencl/opencl.c +++ b/src/modules/opencl/opencl.c @@ -132,5 +132,6 @@ FFModuleBaseInfo ffOpenCLModuleInfo = { { "Platform version", "version" }, { "Platform name", "name" }, { "Platform vendor", "vendor" }, - })) + })), + .defaultOrder = 56, }; diff --git a/src/modules/opengl/opengl.c b/src/modules/opengl/opengl.c index 1ecce858ae..29adcf0f0d 100644 --- a/src/modules/opengl/opengl.c +++ b/src/modules/opengl/opengl.c @@ -141,5 +141,6 @@ FFModuleBaseInfo ffOpenGLModuleInfo = { { "OpenGL vendor", "vendor" }, { "OpenGL shading language version", "slv" }, { "OpenGL library used", "library" }, - })) + })), + .defaultOrder = 55, }; diff --git a/src/modules/os/os.c b/src/modules/os/os.c index ea08a567c7..d0dc5600b7 100644 --- a/src/modules/os/os.c +++ b/src/modules/os/os.c @@ -193,5 +193,6 @@ FFModuleBaseInfo ffOSModuleInfo = { { "Version codename of the OS", "codename" }, { "Build ID of the OS", "build-id" }, { "Architecture of the OS", "arch" }, - })) + })), + .defaultOrder = 3, }; diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index 8cb4027bde..1553893f1a 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -573,5 +573,6 @@ FFModuleBaseInfo ffPackagesModuleInfo = { { "Total number of all hpkg packages", "hpkg-all" }, { "Total number of all nix packages", "nix-all" }, { "Number of all packages", "all" }, - })) + })), + .defaultOrder = 14, }; diff --git a/src/modules/physicaldisk/physicaldisk.c b/src/modules/physicaldisk/physicaldisk.c index b0b7c58607..8935438840 100644 --- a/src/modules/physicaldisk/physicaldisk.c +++ b/src/modules/physicaldisk/physicaldisk.c @@ -284,5 +284,6 @@ FFModuleBaseInfo ffPhysicalDiskModuleInfo = { { "Device kind (Read-only or Read-write)", "readonly-type" }, { "Product revision", "revision" }, { "Device temperature (formatted)", "temperature" }, - })) + })), + .defaultOrder = 68, }; diff --git a/src/modules/physicalmemory/physicalmemory.c b/src/modules/physicalmemory/physicalmemory.c index d4bee7db53..f044e59071 100644 --- a/src/modules/physicalmemory/physicalmemory.c +++ b/src/modules/physicalmemory/physicalmemory.c @@ -180,5 +180,6 @@ FFModuleBaseInfo ffPhysicalMemoryModuleInfo = { { "Part number", "part-number" }, { "True if ECC enabled", "is-ecc-enabled" }, { "True if a memory module is installed in the slot", "is-installed" }, - })) + })), + .defaultOrder = 39, }; diff --git a/src/modules/player/player.c b/src/modules/player/player.c index 7d2248093e..0d1fd03b8f 100644 --- a/src/modules/player/player.c +++ b/src/modules/player/player.c @@ -122,5 +122,6 @@ FFModuleBaseInfo ffPlayerModuleInfo = { { "Player name", "name" }, { "Player Identifier", "id" }, { "URL name", "url" }, - })) + })), + .defaultOrder = 46, }; diff --git a/src/modules/poweradapter/poweradapter.c b/src/modules/poweradapter/poweradapter.c index 12086c9b2d..be20d2a6d7 100644 --- a/src/modules/poweradapter/poweradapter.c +++ b/src/modules/poweradapter/poweradapter.c @@ -125,5 +125,6 @@ FFModuleBaseInfo ffPowerAdapterModuleInfo = { { "Power adapter model", "model" }, { "Power adapter description", "description" }, { "Power adapter serial number", "serial" }, - })) + })), + .defaultOrder = 45, }; diff --git a/src/modules/processes/processes.c b/src/modules/processes/processes.c index d5542bac49..daf7a23ed8 100644 --- a/src/modules/processes/processes.c +++ b/src/modules/processes/processes.c @@ -72,5 +72,7 @@ FFModuleBaseInfo ffProcessesModuleInfo = { .generateJsonResult = (void*) ffGenerateProcessesJsonResult, .generateJsonConfig = (void*) ffGenerateProcessesJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { - { "Process count", "result" } })) + { "Process count", "result" }, + })), + .defaultOrder = 13, }; diff --git a/src/modules/publicip/publicip.c b/src/modules/publicip/publicip.c index 552173395e..5d52e74a70 100644 --- a/src/modules/publicip/publicip.c +++ b/src/modules/publicip/publicip.c @@ -121,5 +121,6 @@ FFModuleBaseInfo ffPublicIPModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Public IP address", "ip" }, { "Location", "location" }, - })) + })), + .defaultOrder = 48, }; diff --git a/src/modules/separator/separator.c b/src/modules/separator/separator.c index e0179b1e9b..bd84d7a40d 100644 --- a/src/modules/separator/separator.c +++ b/src/modules/separator/separator.c @@ -117,6 +117,7 @@ void ffInitSeparatorOptions(FFSeparatorOptions* options) { void ffDestroySeparatorOptions(FFSeparatorOptions* options) { ffStrbufDestroy(&options->string); + ffStrbufDestroy(&options->outputColor); } FFModuleBaseInfo ffSeparatorModuleInfo = { @@ -127,4 +128,5 @@ FFModuleBaseInfo ffSeparatorModuleInfo = { .parseJsonObject = (void*) ffParseSeparatorJsonObject, .printModule = (void*) ffPrintSeparator, .generateJsonConfig = (void*) ffGenerateSeparatorJsonConfig, + .defaultOrder = 2, }; diff --git a/src/modules/shell/shell.c b/src/modules/shell/shell.c index d840135dd1..84db1baca0 100644 --- a/src/modules/shell/shell.c +++ b/src/modules/shell/shell.c @@ -106,5 +106,6 @@ FFModuleBaseInfo ffShellModuleInfo = { { "Shell pretty name", "pretty-name" }, { "Shell full exe path", "exe-path" }, { "Shell tty used", "tty" }, - })) + })), + .defaultOrder = 15, }; diff --git a/src/modules/sound/sound.c b/src/modules/sound/sound.c index bf37c1f1bc..6bc73cbe7b 100644 --- a/src/modules/sound/sound.c +++ b/src/modules/sound/sound.c @@ -215,5 +215,6 @@ FFModuleBaseInfo ffSoundModuleInfo = { { "Identifier", "identifier" }, { "Volume (in percentage bar)", "volume-percentage-bar" }, { "Platform API used", "platform-api" }, - })) + })), + .defaultOrder = 60, }; diff --git a/src/modules/swap/swap.c b/src/modules/swap/swap.c index 78568e717c..cf9d107c60 100644 --- a/src/modules/swap/swap.c +++ b/src/modules/swap/swap.c @@ -196,5 +196,6 @@ FFModuleBaseInfo ffSwapModuleInfo = { { "Percentage used (num)", "percentage" }, { "Percentage used (bar)", "percentage-bar" }, { "Name", "name" }, - })) + })), + .defaultOrder = 40, }; diff --git a/src/modules/terminal/terminal.c b/src/modules/terminal/terminal.c index 979f3303ba..5b2e9305fe 100644 --- a/src/modules/terminal/terminal.c +++ b/src/modules/terminal/terminal.c @@ -100,5 +100,6 @@ FFModuleBaseInfo ffTerminalModuleInfo = { { "Terminal version", "version" }, { "Terminal full exe path", "exe-path" }, { "Terminal tty / pts used", "tty" }, - })) + })), + .defaultOrder = 29, }; diff --git a/src/modules/terminalfont/terminalfont.c b/src/modules/terminalfont/terminalfont.c index 6285b28777..87a0837185 100644 --- a/src/modules/terminalfont/terminalfont.c +++ b/src/modules/terminalfont/terminalfont.c @@ -119,4 +119,5 @@ FFModuleBaseInfo ffTerminalFontModuleInfo = { { "Terminal font size", "size" }, { "Terminal font styles", "styles" }, })), + .defaultOrder = 30, }; diff --git a/src/modules/terminalsize/terminalsize.c b/src/modules/terminalsize/terminalsize.c index 81a9761b37..088b97fcef 100644 --- a/src/modules/terminalsize/terminalsize.c +++ b/src/modules/terminalsize/terminalsize.c @@ -90,4 +90,5 @@ FFModuleBaseInfo ffTerminalSizeModuleInfo = { { "Terminal width (in pixels)", "width" }, { "Terminal height (in pixels)", "height" }, })), + .defaultOrder = 31, }; diff --git a/src/modules/terminaltheme/terminaltheme.c b/src/modules/terminaltheme/terminaltheme.c index 6e7967b35b..3d5239b2f6 100644 --- a/src/modules/terminaltheme/terminaltheme.c +++ b/src/modules/terminaltheme/terminaltheme.c @@ -106,5 +106,6 @@ FFModuleBaseInfo ffTerminalThemeModuleInfo = { { "Terminal foreground type (Dark / Light)", "fg-type" }, { "Terminal background color", "bg-color" }, { "Terminal background type (Dark / Light)", "bg-type" }, - })) + })), + .defaultOrder = 32, }; diff --git a/src/modules/theme/theme.c b/src/modules/theme/theme.c index 4c88b68f76..f51a4aac64 100644 --- a/src/modules/theme/theme.c +++ b/src/modules/theme/theme.c @@ -98,5 +98,6 @@ FFModuleBaseInfo ffThemeModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Theme part 1", "theme1" }, { "Theme part 2", "theme2" }, - })) + })), + .defaultOrder = 24, }; diff --git a/src/modules/title/title.c b/src/modules/title/title.c index ca7343a924..ce877caed7 100644 --- a/src/modules/title/title.c +++ b/src/modules/title/title.c @@ -197,5 +197,6 @@ FFModuleBaseInfo ffTitleModuleInfo = { { "UID (*nix) / SID (Windows)", "user-id" }, { "PID of current process", "pid" }, { "CWD with home dir replaced by `~`", "cwd" }, - })) + })), + .defaultOrder = 1, }; diff --git a/src/modules/tpm/tpm.c b/src/modules/tpm/tpm.c index b6dd62dd89..6596d6261c 100644 --- a/src/modules/tpm/tpm.c +++ b/src/modules/tpm/tpm.c @@ -94,5 +94,6 @@ FFModuleBaseInfo ffTPMModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "TPM device version", "version" }, { "TPM general description", "description" }, - })) + })), + .defaultOrder = 69, }; diff --git a/src/modules/uptime/uptime.c b/src/modules/uptime/uptime.c index dd0d90d2b9..41ac4526a1 100644 --- a/src/modules/uptime/uptime.c +++ b/src/modules/uptime/uptime.c @@ -102,5 +102,6 @@ FFModuleBaseInfo ffUptimeModuleInfo = { { "Days of year after boot", "days-of-year" }, { "Years fraction after boot", "years-fraction" }, { "Formatted uptime", "formatted" }, - })) + })), + .defaultOrder = 11, }; diff --git a/src/modules/users/users.c b/src/modules/users/users.c index 5575bbeaab..fbf1ffaa8e 100644 --- a/src/modules/users/users.c +++ b/src/modules/users/users.c @@ -202,5 +202,6 @@ FFModuleBaseInfo ffUsersModuleInfo = { { "Years integer after login", "years" }, { "Days of year after login", "days-of-year" }, { "Years fraction after login", "years-fraction" }, - })) + })), + .defaultOrder = 57, }; diff --git a/src/modules/version/version.c b/src/modules/version/version.c index 211eece8df..fa3b59fe7f 100644 --- a/src/modules/version/version.c +++ b/src/modules/version/version.c @@ -113,5 +113,6 @@ FFModuleBaseInfo ffVersionModuleInfo = { { "Date time when compiling", "compile-time" }, { "Compiler used when compiling", "compiler" }, { "Libc used when compiling", "libc" }, - })) + })), + .defaultOrder = 70, }; diff --git a/src/modules/vulkan/vulkan.c b/src/modules/vulkan/vulkan.c index abfd3612de..03123b7ae6 100644 --- a/src/modules/vulkan/vulkan.c +++ b/src/modules/vulkan/vulkan.c @@ -145,5 +145,6 @@ FFModuleBaseInfo ffVulkanModuleInfo = { { "API version", "api-version" }, { "Conformance version", "conformance-version" }, { "Instance version", "instance-version" }, - })) + })), + .defaultOrder = 54, }; diff --git a/src/modules/wallpaper/wallpaper.c b/src/modules/wallpaper/wallpaper.c index 6d11029531..b5fab48f50 100644 --- a/src/modules/wallpaper/wallpaper.c +++ b/src/modules/wallpaper/wallpaper.c @@ -86,5 +86,6 @@ FFModuleBaseInfo ffWallpaperModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "File name", "file-name" }, { "Full path", "full-path" }, - })) + })), + .defaultOrder = 28, }; diff --git a/src/modules/weather/weather.c b/src/modules/weather/weather.c index 6c156929d8..152cb057bd 100644 --- a/src/modules/weather/weather.c +++ b/src/modules/weather/weather.c @@ -87,6 +87,7 @@ void ffInitWeatherOptions(FFWeatherOptions* options) { void ffDestroyWeatherOptions(FFWeatherOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); + ffStrbufDestroy(&options->location); ffStrbufDestroy(&options->outputFormat); } @@ -101,5 +102,6 @@ FFModuleBaseInfo ffWeatherModuleInfo = { .generateJsonConfig = (void*) ffGenerateWeatherJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Weather result", "result" }, - })) + })), + .defaultOrder = 65, }; diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c index 0a23cc2d79..0e9df22c98 100644 --- a/src/modules/wifi/wifi.c +++ b/src/modules/wifi/wifi.c @@ -235,5 +235,6 @@ FFModuleBaseInfo ffWifiModuleInfo = { { "Connection signal quality (percentage bar)", "signal-quality-bar" }, { "Connection channel number", "channel" }, { "Connection channel band in GHz", "band" }, - })) + })), + .defaultOrder = 51, }; diff --git a/src/modules/wm/wm.c b/src/modules/wm/wm.c index da492d3dea..5e23826e1c 100644 --- a/src/modules/wm/wm.c +++ b/src/modules/wm/wm.c @@ -134,5 +134,6 @@ FFModuleBaseInfo ffWMModuleInfo = { { "WM protocol name", "protocol-name" }, { "WM plugin name", "plugin-name" }, { "WM version", "version" }, - })) + })), + .defaultOrder = 22, }; diff --git a/src/modules/wmtheme/wmtheme.c b/src/modules/wmtheme/wmtheme.c index a45c9baa3c..f1ab4105f7 100644 --- a/src/modules/wmtheme/wmtheme.c +++ b/src/modules/wmtheme/wmtheme.c @@ -71,5 +71,6 @@ FFModuleBaseInfo ffWMThemeModuleInfo = { .generateJsonConfig = (void*) ffGenerateWMThemeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "WM theme", "result" }, - })) + })), + .defaultOrder = 23, }; diff --git a/src/modules/zpool/zpool.c b/src/modules/zpool/zpool.c index cddd1c7054..7e5f03a52d 100644 --- a/src/modules/zpool/zpool.c +++ b/src/modules/zpool/zpool.c @@ -214,5 +214,6 @@ FFModuleBaseInfo ffZpoolModuleInfo = { { "Size allocated percentage bar", "allocated-percentage-bar" }, { "Fragmentation percentage bar", "frag-percentage-bar" }, { "Is read-only", "is-readonly" }, - })) + })), + .defaultOrder = 43, }; diff --git a/src/options/display.c b/src/options/display.c index a60286c865..a1136e7b1a 100644 --- a/src/options/display.c +++ b/src/options/display.c @@ -897,6 +897,19 @@ void ffOptionsDestroyDisplay(FFOptionsDisplay* options) { ffStrbufDestroy(&options->keyValueSeparator); ffStrbufDestroy(&options->barCharElapsed); ffStrbufDestroy(&options->barCharTotal); + ffStrbufDestroy(&options->barBorderLeft); + ffStrbufDestroy(&options->barBorderRight); + ffStrbufDestroy(&options->barBorderLeftElapsed); + ffStrbufDestroy(&options->barBorderRightElapsed); + ffStrbufDestroy(&options->barColorElapsed); + ffStrbufDestroy(&options->barColorTotal); + ffStrbufDestroy(&options->barColorBorder); + ffStrbufDestroy(&options->tempColorGreen); + ffStrbufDestroy(&options->tempColorYellow); + ffStrbufDestroy(&options->tempColorRed); + ffStrbufDestroy(&options->percentColorGreen); + ffStrbufDestroy(&options->percentColorYellow); + ffStrbufDestroy(&options->percentColorRed); FF_LIST_FOR_EACH (FFstrbuf, item, options->constants) { ffStrbufDestroy(item); } diff --git a/src/options/general.c b/src/options/general.c index d1ee8e7818..ee7204218b 100644 --- a/src/options/general.c +++ b/src/options/general.c @@ -24,12 +24,7 @@ const char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_va } else if (unsafe_yyjson_equals_str(key, "processingTimeout")) { options->processingTimeout = (int32_t) yyjson_get_int(val); } else if (unsafe_yyjson_equals_str(key, "preRun")) { - if (!yyjson_is_str(val)) { - return "general.preRun must be a string"; - } - if (system(unsafe_yyjson_get_str(val)) < 0) { - return "Failed to execute preRun command"; - } + return "general.preRun is removed due to security concerns."; } else if (unsafe_yyjson_equals_str(key, "detectVersion")) { options->detectVersion = yyjson_get_bool(val); } else if (unsafe_yyjson_equals_str(key, "playerName")) { diff --git a/tests/list.c b/tests/list.c index 2184dd79c3..a42d088780 100644 --- a/tests/list.c +++ b/tests/list.c @@ -100,6 +100,79 @@ int main(void) { VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); VERIFY(*FF_LIST_GET(uint32_t, list, list.length - 1) == FF_LIST_DEFAULT_ALLOC); + // insertAt + ffListClear(&list); + for (uint32_t i = 1; i <= 5; ++i) { + *FF_LIST_ADD(uint32_t, list) = i; + } + // list = [1,2,3,4,5] + + { + uint32_t v = 0; + FF_LIST_INSERT_AT(uint32_t, list, 0, &v); // insert at head + } + VERIFY(list.length == 6); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 0); + VERIFY(*FF_LIST_GET(uint32_t, list, 1) == 1); + VERIFY(*FF_LIST_GET(uint32_t, list, 5) == 5); + // list = [0,1,2,3,4,5] + + { + uint32_t v = 99; + FF_LIST_INSERT_AT(uint32_t, list, 3, &v); // insert in the middle + } + VERIFY(list.length == 7); + VERIFY(*FF_LIST_GET(uint32_t, list, 2) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 3) == 99); + VERIFY(*FF_LIST_GET(uint32_t, list, 4) == 3); + VERIFY(*FF_LIST_GET(uint32_t, list, 6) == 5); + // list = [0,1,2,99,3,4,5] + + { + uint32_t v = 6; + FF_LIST_INSERT_AT(uint32_t, list, list.length, &v); // insert at tail + } + VERIFY(list.length == 8); + VERIFY(*FF_LIST_GET(uint32_t, list, 3) == 99); + VERIFY(*FF_LIST_GET(uint32_t, list, 7) == 6); + // list = [0,1,2,99,3,4,5,6] + + ffListClear(&list); + { + uint32_t v = 42; + FF_LIST_INSERT_AT(uint32_t, list, 0, &v); // insert into empty list + } + VERIFY(list.length == 1); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 42); + + // removeAt + FF_LIST_REMOVE_AT(uint32_t, list, 0); // remove the only element + VERIFY(list.length == 0); + + for (uint32_t i = 1; i <= 5; ++i) { + *FF_LIST_ADD(uint32_t, list) = i; + } + // list = [1,2,3,4,5] + + FF_LIST_REMOVE_AT(uint32_t, list, 0); // remove head + VERIFY(list.length == 4); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, list.length - 1) == 5); + // list = [2,3,4,5] + + FF_LIST_REMOVE_AT(uint32_t, list, 1); // remove in the middle + VERIFY(list.length == 3); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 1) == 4); + VERIFY(*FF_LIST_GET(uint32_t, list, 2) == 5); + // list = [2,4,5] + + FF_LIST_REMOVE_AT(uint32_t, list, list.length - 1); // remove tail + VERIFY(list.length == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 1) == 4); + // list = [2,4] + // Destroy ffListDestroy(&list); diff --git a/tests/strbuf.c b/tests/strbuf.c index 6007ff9087..a8e698cafc 100644 --- a/tests/strbuf.c +++ b/tests/strbuf.c @@ -414,7 +414,7 @@ int main(void) { ffStrbufInit(&strbuf); ffStrbufEnsureFixedLengthFree(&strbuf, 0); VERIFY(strbuf.length == 0); - VERIFY(strbuf.allocated == 0); + VERIFY(strbuf.allocated == 1); ffStrbufDestroy(&strbuf); // ffStrbufEnsureFixedLengthFree / empty buffer but oldFree >= newFree