diff --git a/.github/ISSUE_TEMPLATE/logo_request.yml b/.github/ISSUE_TEMPLATE/logo_request.yml index fa0570dab..d3d605d57 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 internal padding (spaces at the start and/or end of lines). + - label: The ASCII art has no unnecessary trailing spaces at the end of lines. + required: true + - 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 041bc4ff2..a6af2cba0 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 000000000..25b140d8f --- /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 00d37612d..46ac9d4fa 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 9cb47a237..8ab97abc3 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 56ef392e0..4cd0df0d1 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 a55f2798d..d2b0a53d9 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 da6732fbc..e24d294fc 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 e9271b2ff..4197c241b 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 cf1377b27..630b0734b 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 7b40bb42d..e3722ad82 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 74b792925..9fb18210f 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 cf0c04305..0f03a912b 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 133ec5b19..e6d4fae62 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 76857a1ce..fe2d4f66f 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 000000000..91f136a3d --- /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 35747f8fe..2ed3e9de8 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 98a415ffb..fa987c303 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 eacf4eab9..c4225a711 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 978011bd1..d70f1faa5 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 000000000..e60d8b5f1 --- /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 9feee1bf5..5179d5b5d 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 6593899a6..5e890dda0 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 dbee39a69..770c0ce77 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 ba4af1dc8..409edff7e 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 872bfc9fa..06003cc93 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 9f54d3c1a..e2697042c 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 98e21a2e6..9eb9f7dca 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 36ca55a99..2cf2a48b2 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 91beb688e..bce7d55b2 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 e3c801853..6639a70c2 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 d26cfbc68..7c49d858c 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 7e37aee8d..29c1d46e9 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 a661936cf..e225eb881 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 494225298..f3775bc5a 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 9a6b95ad7..526ff66a5 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); - } else { - FF_DEBUG("Failed to get PCI address"); - } + 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 PCI bus number"); + 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 device description: %s", ffDebugConfigRet(ret)); + } } - if (queryPcieGen) { + 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 device vendor name: %s", ffDebugConfigRet(ret)); + } + + 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); + bool isIndirectDisplayDevice = gpu->type == FF_GPU_TYPE_UNKNOWN && adapterType.IndirectDisplayDevice; + if (isIndirectDisplayDevice) { + FF_DEBUG("Adapter #%u is an indirect display device", i); } -#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); - } - - #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 = 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), - }); + status = D3DKMTQueryStatistics(&queryStatistics); 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; + 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("Failed to query node performance data for adapter #%u node #%u: %s", + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s", i, - nodeIdx, 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); + 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("Adapter #%u does not report temperature data", i); + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); } } else { - FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status)); + // 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 (gpu->type == FF_GPU_TYPE_UNKNOWN) { - if (ffGPUDetectTypeByVendorAndName(gpu)) { - // OK + 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)); + } + } + } + + 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 _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 000000000..f41a66cfa --- /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 20bbeda00..c3d866eb4 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 2c6cf74f3..b3fc2309b 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 a7b1ffa3f..02dcb240a 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 74c592035..c0e8afb47 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 e3b43a52a..b7c2d0a5a 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 97cb9c3ef..fa323aa9e 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 68372eb0c..4c82d2c04 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"); + 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"); - } - } else { - if (ver >= 10) { - ffStrbufSetStatic(&result->theme1, "Fluent"); - } else if (ver >= 8) { - ffStrbufSetStatic(&result->theme1, "Metro"); - } 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 cad134e15..b6c2ea201 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 3e5fd9108..11e3672d8 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 55dcaa97c..000000000 --- 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 eaa0ae036..921d88cb9 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 95ee07c98..000000000 --- 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 9d471eb73..d4efd6cc4 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 5b7d4dddf..000000000 --- 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 6113b0929..c000080f4 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 6721c336d..42a251886 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 9efc0a057..fc113fc83 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 bc8f37cae..1a85ea393 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 05cb46713..494885fd6 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 7bca2b61c..011458a28 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 1d4971fcb..479a2ccb9 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 d9661c2e3..21068e85d 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 08cb10706..02c454bbb 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 1cd308909..9b5b21e7a 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 5d3cbac90..a9a465bf8 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 43cdffb31..8d225d4bf 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 751b97139..4de947a79 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 51f84c9f8..2716b2e03 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 9753e4127..7dd39d1cb 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 5c01e914a..ea592a273 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 748f78cc7..1be3ac6e3 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 8e47e147c..14e3b8251 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 ad714d6f5..8e301fa32 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 72192e260..74b648855 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 3ff31fad4..372910d10 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 bdcec58ea..eceb9a2d1 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 79df55bac..396c3bd43 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 352661ecb..342b31812 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 14cfa8fb1..be940b32d 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 5404cb8c2..f3c3bdfe4 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 163fca8fb..19ba8c868 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 680ba8849..041ab712e 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 d52ddc24b..15dea0928 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 faab272a3..7a02e0309 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 0c40d39f6..9ae5878df 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 5e3f5cbfe..76e6c36f9 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 b8e7c1fc3..ed945bad4 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 61939f515..a6fb6f800 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 9c25341aa..cb9c8e7ec 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 952d72ef6..eeb304e28 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 884d8e452..5f926d115 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 efdf65643..73a8fc173 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 41beeaba2..6c8bed170 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 37e78be9a..82dd6b053 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 17047fffd..8607a21af 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 acfe861cb..0a80d9d16 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 1ecce858a..29adcf0f0 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 ea08a567c..d0dc5600b 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 8cb4027bd..1553893f1 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 b0b7c5860..893543884 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 d4bee7db5..f044e5907 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 7d2248093..0d1fd03b8 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 12086c9b2..be20d2a6d 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 d5542bac4..daf7a23ed 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 552173395..5d52e74a7 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 e0179b1e9..bd84d7a40 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 d840135dd..84db1baca 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 bf37c1f1b..6bc73cbe7 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 78568e717..cf9d107c6 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 979f3303b..5b2e9305f 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 6285b2877..87a083718 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 81a9761b3..088b97fce 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 6e7967b35..3d5239b2f 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 4c88b68f7..f51a4aac6 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 ca7343a92..ce877caed 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 b6dd62dd8..6596d6261 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 dd0d90d2b..41ac4526a 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 5575bbeaa..fbf1ffaa8 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 211eece8d..fa3b59fe7 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 abfd3612d..03123b7ae 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 6d1102953..b5fab48f5 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 6c156929d..152cb057b 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 0a23cc2d7..0e9df22c9 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 da492d3de..5e23826e1 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 a45c9baa3..f1ab4105f 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 cddd1c705..7e5f03a52 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 a60286c86..a1136e7b1 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 d1ee8e781..ee7204218 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 2184dd79c..a42d08878 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 6007ff908..a8e698caf 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