# Contributing to fastfetch
Note This document is generated by AI, and mainly for AI use. It is not a substitute for human review, and may contain errors or omissions. Please verify any information before relying on it.
Thanks for your interest in fastfetch. This document covers building, architecture, how to add a module or a logo, code style, commit conventions, and the pull request workflow. fastfetch is a system information tool written in C23, supporting Linux, macOS, Windows, the BSDs, Solaris, Haiku and Android. The project is borderline obsessive about **startup time** and **optional dependencies** — many design decisions only make sense under that constraint, and this document keeps coming back to it. --- ## Table of contents - [Contributor quick reference](#contributor-quick-reference) - [Getting started](#getting-started) - [Code map](#code-map) - [Core architecture: modules vs. detection](#core-architecture-modules-vs-detection) - [Module registration](#module-registration) - [Walkthrough: adding a module](#walkthrough-adding-a-module) - [Walkthrough: adding a logo](#walkthrough-adding-a-logo) - [Platform conventions](#platform-conventions) - [Code style](#code-style) - [Commit messages](#commit-messages) - [Changelog](#changelog) - [Tests](#tests) - [Pull requests](#pull-requests) - [Gotchas](#gotchas) --- ## Contributor quick reference | Task | Start here | Verify with | |---|---|---| | Build and run fastfetch | `run.sh`, `CMakeLists.txt` | `./run.sh` | | Add a module | `src/modules//`, `src/detection//`, `src/modules/modules.c` | `cmake -B build && cmake --build build -j` | | Add a platform implementation | `src/detection//_.c` | Build on the target platform or CI | | Add an ASCII logo | `src/logo/ascii//.txt`, matching `.inc` | `./build/fastfetch --logo ` | | Change formatting or JSON output | `src/modules//.c` | `./build/fastfetch -s --format json` | | Change shared formatting or containers | `src/common/format.h`, `src/common/color.h`, `src/common/FFstrbuf.h` | Build and run the matching test | | Run the test suite | `tests/`, `build/` | `cd build && ctest --output-on-failure` | For a normal code change, the shortest useful loop is: configure, build the `fastfetch` target, run the affected module, then run the tests. If you add a directory or a generated input, re-run `cmake -B build` so CMake refreshes its file globs. --- ## Getting started ### Building The quickest way is `run.sh` in the repository root — it creates `build/`, configures, compiles and runs the binary: ```sh ./run.sh # build and run ./run.sh --format json # arguments are forwarded to fastfetch ``` Manual build: ```sh cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo cmake --build build --target fastfetch -j$(nproc) ./build/fastfetch ``` The default build type is `RelWithDebInfo`. LTO is enabled whenever `ENABLE_LTO=ON` **and** `CMAKE_BUILD_TYPE != Debug` — so the default already has it, and only `Debug` builds lack it. This matters because LTO here is not merely an optimization: it is what strips the code of disabled modules. See [Gotchas](#gotchas). ### Common build options | Option | Default | Notes | |---|---|---| | `CMAKE_BUILD_TYPE` | `RelWithDebInfo` | LTO is on for anything but `Debug` | | `BUILD_TESTS` | `OFF` | Builds the unit tests in `tests/` | | `BUILD_FLASHFETCH` | `ON` | Also builds the stripped-down `flashfetch` | | `BINARY_LINK_TYPE` | `dlopen` | `dlopen` / `dynamic` / `static` | | `ENABLE_ASAN` | `OFF` | Address Sanitizer | | `ENABLE_LTO` | `ON` | Link-time optimization | | `MODULE_DISABLE_` | `OFF` | Disable a module, e.g. `MODULE_DISABLE_GPU=ON` | | `SET_TWEAK` | `ON` | Appends a tweak to the dev version; turned off for releases | `BINARY_LINK_TYPE=dlopen` (the default) is a deliberate design choice: optional dependencies (Vulkan, Wayland, DBus, ImageMagick, chafa, …) are loaded at runtime, so a missing library never prevents startup. When adding a third-party dependency, use the dlopen helpers in `common/library.h` — do not link it directly. For a minimal-dependency build, see `.github/workflows/build-no-features-test.yml`: ```sh cmake -DBUILD_TESTS=On -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_X11=OFF \ -DENABLE_DBUS=OFF -DENABLE_ZLIB=OFF ... . ``` ### Dependencies Required: CMake ≥ 3.21 and a C23 compiler (GCC, Clang or MSVC). Everything else is optional. Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, pulse, ddcutil, elf, libzfs, and more. --- ## Code map ``` src/ ├── fastfetch.c entry point: CLI parsing, module dispatch, main loop (36 KB) ├── flashfetch.c entry point for the stripped-down build ├── options/ global (non-module) config: general / display / logo ├── modules/ module layer — module formatting and output ├── detection/ detection layer — platform-specific data retrieval ├── common/ infrastructure: FFstrbuf, FFlist, formatting, printing, networking ├── logo/ 530 ASCII logos plus the image logo backends └── 3rdparty/ yyjson, widecharwidth, display-library ``` Flow of control: ``` fastfetch.c → options/ (global configuration) → modules/ (76 modules, formatting and output) → detection/ (one platform implementation each, raw data) → common/ (infrastructure) ``` The number of module directories and detection directories does not match, and that is expected. The following relationships are structural rather than a fixed inventory: - **13 modules have no detection directory** — they are either pure layout (`break`, `separator`, `colors`, `title`, `logo`) or reuse someone else's detection result (`display`, `monitor`, `kernel`, `shell`, `terminal`, `player`, `custom`, `datetime`). - **4 detection directories serve modules with different names** — `displayserver` (→ `display`, `monitor`), `gtk_qt` (→ `theme`, `icons`, `font`, `cursor`), `terminalshell` (→ `terminal`, `shell`) and `libc`. So do not assume `modules//` always has a matching `detection//`. ### Layer boundaries | Layer | Owns | Must not | |---|---|---| | `options/` | **Global** config (colors, logo, threading, timeouts) | hold per-module options | | `modules/` | formatting, printing, JSON serialization | read `/proc`, call Win32 APIs, parse sysfs | | `detection/` | cross-platform data retrieval, clean result structs | print anything, read display config | | `common/` | general-purpose utilities, no business logic | depend on a specific module | The test is simple: **no file under `detection/` should contain `printf` or read `instance.config`.** Symmetrically, no file under `modules/` should contain `#ifdef __linux__`. --- ## Core architecture: modules vs. detection This is the single most important convention in the codebase. Every feature is a fixed set of files — using CPU as the example: ``` modules/cpu/option.h module option struct modules/cpu/cpu.c formatting and output detection/cpu/cpu.h platform-independent interface detection/cpu/cpu_linux.c ┐ detection/cpu/cpu_apple.c │ detection/cpu/cpu_windows.c ├─ platform implementations, one linked per build detection/cpu/cpu_bsd.c │ detection/cpu/cpu_nosupport.c ┘ ``` The interface is deliberately minimal — usually just two things: ```c typedef struct FFCPUResult { ... } FFCPUResult; // result struct const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu); ``` The `const char*` return value is an **error string**; `nullptr` means success. This pattern is used throughout `detection/` — please keep it consistent. Helper functions are only exposed when they are genuinely shared between platform implementations (as in `cpu.h`: `ffCPUAppleCodeToName`, `ffCPUDetectByCpuid`). **The payoff:** adding a platform never touches `modules/`; fixing output formatting never touches `detection/`. --- ## Module registration ### The descriptor Every module exports one `FFModuleBaseInfo` (defined in `common/option.h:46`) — essentially a hand-written vtable: ```c typedef struct FFModuleBaseInfo { const char* name; // lookup key, e.g. "cpu" const char* description; // help text FFModuleDisplayName displayName; // module name in 20 languages void (*initOptions)(void* options); void (*destroyOptions)(void* options); void (*parseJsonObject)(void* options, struct yyjson_val* module); bool (*printModule)(void* options); bool (*generateJsonResult)(void* options, yyjson_mut_doc*, yyjson_mut_val*); void (*generateJsonConfig)(void* options, yyjson_mut_doc*, yyjson_mut_val*); FFModuleFormatArgList formatArgs; // format placeholder table const uint8_t defaultOrder; // sort weight } FFModuleBaseInfo; ``` The source comment openly admits this is UB — `void*` is not compatible with `FF*Options*`. It is a pragmatic compromise to get polymorphism in C. Don't try to "fix" it. ### The registry: a first-letter hash bucket `src/modules/modules.c` defines 26 `static FFModuleBaseInfo*` arrays (`A[]` through `Z[]`), each terminated by `nullptr`, collected into `ffModuleInfos[26]`: ```c FFModuleBaseInfo** modules = ffModuleInfos[toupper(name[0]) - 'A']; // O(1) bucket lookup for (; *modules; ++modules) { // linear scan in the bucket if (ffStrEqualsIgnCase(name, (*modules)->name)) { ... } } ``` The registry is small enough that a subtraction plus a few string comparisons is preferable to a general-purpose hash table. When adding a module, place its descriptor in the bucket matching the first letter of `.name`; keep the existing `nullptr` terminator at the end. ### The calling convention: zero heap allocation Every dispatch site (`jsonconfig.c:98`, `commandoption.c:189`) follows the same pattern: ```c alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; // 256 bytes, on the stack baseInfo->initOptions(optionBuf); baseInfo->parseJsonObject(optionBuf, jsonVal); // JSONC path only baseInfo->printModule(optionBuf); // or generateJsonResult baseInfo->destroyOptions(optionBuf); ``` `FF_OPTION_MAX_SIZE = 1 << 8` (256 bytes). Every module's `option.h` must end with: ```c static_assert(sizeof(FFCPUOptions) <= FF_OPTION_MAX_SIZE, "FFCPUOptions size exceeds maximum allowed size"); ``` **Meaning: no module option struct may exceed 256 bytes.** Hard constraint, enforced at compile time. The three dispatch entry points: | Entry point | Location | Used for | |---|---|---| | `parseModuleJsonObject` | `common/impl/jsonconfig.c:90` | the `modules[]` array in a JSONC config | | `parseStructureCommand` | `common/impl/commandoption.c:181` | the colon-separated structure string | | `ffParseModuleOptions` | `common/impl/commandoption.c:13` | **deprecated** — see [Gotchas](#4-cli-module-options-are-removed) | ### Build-time module discovery `CMakeLists.txt:131` globs `src/modules/*/` and generates a `MODULE_DISABLE_` option per directory: ```cmake file(GLOB FF_MODULE_DIRS RELATIVE "..." ".../src/modules/*/") foreach(FF_MODULE_DIR ${FF_MODULE_DIRS}) option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF) endforeach() ``` **Adding a module requires no CMake change.** The same trick generates the package manager switches (`:146` regex-scans `src/modules/packages/option.h` for `FF_PACKAGES_FLAG_*_BIT`). --- ## Walkthrough: adding a module Adding a `Foo` module, step by step. ### 1. Define the option struct `src/modules/foo/option.h`: ```c #pragma once #include "common/option.h" typedef struct FFFooOptions { FFModuleArgs moduleArgs; // must be the first field bool showBar; } FFFooOptions; static_assert(sizeof(FFFooOptions) <= FF_OPTION_MAX_SIZE, "FFFooOptions size exceeds maximum allowed size"); ``` `FFModuleArgs` must come first. It provides `key`, `format`, `outputColor`, `keyColor`, `keyIcon` and `keyWidth`; because it is the first field, `ffJsonConfigParseModuleArgs()` can handle these generically and you get them **for free — no parsing code to write**. Note these fields can only be set through the JSON config; CLI module options have been removed (see [Gotchas](#4-cli-module-options-are-removed)). ### 2. Implement detection `src/detection/foo/foo.h`: ```c #pragma once #include "fastfetch.h" typedef struct FFFooResult { FFstrbuf name; uint32_t count; } FFFooResult; const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result); ``` `src/detection/foo/foo_linux.c`: ```c #include "foo.h" const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result) { // read /sys, /proc, sysfs, dbus, ... return nullptr; // success; return an error string on failure } ``` **Write one file per target platform, and a `foo_nosupport.c` fallback for the rest.** There are already 45 `*_nosupport.c` files doing exactly this. ### 3. Implement the module `src/modules/foo/foo.c` — implement the six functions, then define the descriptor: ```c FFModuleBaseInfo ffFooModuleInfo = { .name = "Foo", .description = "Print foo information", .displayName = { .en = "Foo", .ar = "Foo", .cs = "Foo", .de = "Foo", .es = "Foo", .fr = "Foo", .gl = "Foo", .he = "Foo", .id = "Foo", .it = "Foo", .ja = "Foo", .ko = "Foo", .pl = "Foo", .pt = "Foo", .ru = "Foo", .tr = "Foo", .uk = "Foo", .vi = "Foo", .zh_CN = "Foo", .zh_TW = "Foo", }, .initOptions = (void*) ffInitFooOptions, .destroyOptions = (void*) ffDestroyFooOptions, .parseJsonObject = (void*) ffParseFooJsonObject, .printModule = (void*) ffPrintFoo, .generateJsonResult = (void*) ffGenerateFooJsonResult, .generateJsonConfig = (void*) ffGenerateFooJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Name", "name" }, { "Count", "count" }, })), .defaultOrder = 73, }; ``` Points to note: - **`displayName`** is a literal block of all 20 language fields — there is no shortcut macro, so copy the layout from a neighboring module: **`en`, `ar`, `cs`, `de`, `es`, `fr`, `gl`, `he`, `id`, `it`, `ja`, `ko`, `pl`, `pt`, `ru`, `tr`, `uk`, `vi`, `zh_CN`, `zh_TW`**. **Fill in all 20.** The struct is read by byte offset (see [Gotchas](#6-localization-uses-byte-offsets-not-enums)) and there is **no fallback** — a missing field means the key prints empty in that language. - **`formatArgs`** must be exhaustive. It drives the placeholder list printed by `fastfetch -h foo-format`; **anything you omit is invisible and unusable to the user.** - The `moduleFormat` section in `doc/json_schema.json` is generated by `fastfetch -h format-json`. Its metadata comes from `FFModuleBaseInfo::formatArgs`. To keep the schema and module descriptors consistent, do not edit the `moduleFormat` section in `doc/json_schema.json` by hand; update the module metadata and regenerate it instead. - **`defaultOrder`**: use the current maximum plus 1. Search the existing descriptors for `.defaultOrder =` before choosing a value; do not copy a hard-coded value from this document. Leaving it out, or setting it to `0`, makes the module **disappear** from the interactive `--gen-config` picker. Only `logo`, `command` and `custom` do this deliberately, because they need user arguments or are invoked directly by the display layer. ### 4. Register it - Add `#include "modules/foo/foo.h"` to `src/modules/modules.h` - Insert into the `F[]` array in `src/modules/modules.c`: ```c #if !FF_MODULE_DISABLE_FOO &ffFooModuleInfo, #endif ``` `FF_MODULE_DISABLE_FOO` is generated by CMake — you do not define it yourself. ### 5. Modules that need warm-up If your module needs a sampling interval (CPU usage) or a network round-trip (public IP, weather), also implement `ffPrepareFoo()` and register it in the switch inside `ffPrepareCommandOption()` in `common/impl/commandoption.c`, under the right first-letter `case`. Six modules do this today: CPUUsage, DiskIO, NetIO, PublicIP, Top and Weather. ### 6. Verify ```sh cmake -B build && cmake --build build -j ./build/fastfetch -s foo --format json # JSON output ./build/fastfetch -h foo-format # list formatArgs placeholders ./build/fastfetch --gen-config # confirm it appears in the picker ``` Note the `-format` suffix on the help flag: `fastfetch -h foo` does not work, only `fastfetch -h foo-format`. --- ## Walkthrough: adding a logo A new logo **must** have a corresponding "Logo Request" issue, linked from the PR with `Closes #1234`. Logo PRs without a linked issue are not accepted. ### 1. Drop in the ASCII file ``` src/logo/ascii//.txt ``` For example `src/logo/ascii/o/omarchy.txt`. Directories are already split by first letter (`a/` … `z/`, plus `_/`). The file is plain ASCII art with `$1` … `$9` as color placeholders: ``` $3 ,-^-___ $3 /\\\/// $2refined.$1 /\\\\// ``` - `$1` … `$9` map to palette slots 1–9; at most 9 (`FASTFETCH_LOGO_MAX_COLORS = 9`) - `$$` is a literal `$` - Characters without a placeholder inherit the current color. Of the 530 existing logos, 239 use no placeholders at all (monochrome) and 291 do — **new logos should use placeholders**; monochrome is a legacy style - Tabs are expanded to 4 spaces - Colors can be overridden with `--logo-color-1` … `--logo-color-9` ### 2. Register it in the `.inc` CMake turns each `.txt` into a `FASTFETCH_DATATEXT_LOGO_` macro (`CMakeLists.txt:418`), but **the registry itself is maintained by hand**. Edit `src/logo/ascii/.inc`: ```c #ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY // Omarchy { .names = { "omarchy", "Omarchy" }, .lines = FASTFETCH_DATATEXT_LOGO_OMARCHY, .colors = { FF_COLOR_FG_BLUE, FF_COLOR_FG_WHITE, FF_COLOR_FG_CYAN, }, }, #endif ``` - `names` is matched case-insensitively. Do not add names that differ only by case, because they can never be distinguished. Every name is also shown by `--list-logos`, so use user-friendly spelling such as an initial capital where appropriate. - For a new logo, use exactly one name unless there is a specific compatibility reason to add more. That name should first be the `ID` from `/etc/os-release`. If the ID cannot distinguish this logo from another logo for the same OS, use the `NAME` value instead. `logoGetBuiltinDetected` in `src/logo/logo.c` documents the detection order: `ID`, then `NAME`, then tokens from `ID_LIKE`, then the platform name fallback. Manual `logo-source` selection uses the name directly. - `colors` is positional — entry 0 is `$1`, entry 1 is `$2`, and so on - `colors[0]` also becomes the title color and `colors[1]` the key color, unless the user overrides them If the same OS has multiple logo variants, mark the variant explicitly with `.type`: ```c .type = FF_LOGO_LINE_TYPE_SMALL_BIT, ``` Use `FF_LOGO_LINE_TYPE_ALTER_BIT` for an alternate logo, `FF_LOGO_LINE_TYPE_SMALL_BIT` for a small logo, or combine the flags when both apply. This is also a lookup optimization: a logo marked `FF_LOGO_LINE_TYPE_SMALL_BIT` is considered only for `type = small`, while a logo marked `FF_LOGO_LINE_TYPE_ALTER_BIT` is never selected by automatic detection. Alternate logos are available only when explicitly requested through `--logo `. ### 3. Verify ```sh ./build/fastfetch --logo omarchy ./build/fastfetch --list-logos | grep -i omarchy ``` --- ## Platform conventions The `detection/` layer picks its implementation by **filename suffix**; the choice is made at build time from `CMAKE_SYSTEM_NAME`: | Suffix | Platform | |---|---| | `_linux.c` | Linux | | `_apple.c` / `_apple.m` | macOS / iOS (`.m` for Objective-C) | | `_windows.c` / `_windows.cpp` | Windows (`.cpp` for WinRT / WMI) | | `_bsd.c` | FreeBSD | | `_nbsd.c` | NetBSD | | `_obsd.c` | OpenBSD | | `_sunos.c` | Solaris / illumos | | `_haiku.c` / `.cpp` | Haiku | | `_android.c` | Android (Termux) | | `_gnu.c` | GNU/Hurd | | `_nosupport.c` | Empty fallback | Current distribution of platform files in `detection/`: | Suffix | Files | Suffix | Files | |---|---|---|---| | `_linux` | 58 | `_obsd` | 16 | | `_windows` | 47 | `_haiku` | 13 | | `_apple` | 46 | `_android` | 12 | | `_nosupport` | 45 | `_gnu` | 3 | | `_bsd` | 29 | | | | `_sunos` | 19 | | | | `_nbsd` | 17 | | | (These are repository inventory figures, counting `.c`, `.m`, `.cpp`, and `.h`; they may change as platforms and modules are added.) **Don't pile `#ifdef __linux__` into one file.** When adding platform support, copy the closest existing implementation and change the suffix. `common/` uses the same idea: `common/impl/` contains `io_unix.c` / `io_windows.c`, `netif_linux.c` / `netif_apple.c` / `netif_bsd.c` and so on, with `common/apple/`, `common/windows/` and `common/haiku/` holding platform-specific helpers. When several platform suffixes could apply, use the most specific implementation supported by the build system (for example, `_nbsd.c` instead of the generic `_bsd.c` on NetBSD). Keep the generic file as the fallback for platforms that share its conventions. --- ## Code style ### Formatting The project uses clang-format with `BasedOnStyle: LLVM` plus these key overrides: ```yaml IndentWidth: 4 UseTab: Never ColumnLimit: 0 # never wrap InsertBraces: true # braces even on single-statement ifs PointerAlignment: Left # char* p, not char *p SortIncludes: Never # include order is managed by hand AlignAfterOpenBracket: DontAlign BinPackParameters: false # all params on one line, or one per line ``` Before committing: ```sh clang-format -i src/modules/foo/*.c src/modules/foo/*.h ``` `src/3rdparty/**`, `build/**` and `src/logo/builtin.c` are listed in `.clang-format-ignore` — **do not reformat them.** `.editorconfig` adds LF line endings, 4-space indent, a final newline, and trailing whitespace trimmed (except in Markdown). ### Naming | Kind | Convention | Example | |---|---|---| | Functions | `ff` + PascalCase | `ffDetectCPU`, `ffPrintCPU` | | Types | `FF` + PascalCase | `FFCPUResult`, `FFModuleBaseInfo` | | Variables / fields | camelCase | `coresPhysical`, `keyWidth` | | Macros | SCREAMING_SNAKE_CASE | `FF_OPTION_MAX_SIZE` | | Module options | `FFOptions` | `FFCPUOptions` | | Detection results | `FFResult` | `FFCPUResult` | ### Spelling CI runs codespell (`.codespellrc`). Known false positives live in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code. ### Compiler warnings The build enables `-Wall -Wextra -Wconversion` plus several `-Werror`s: ``` -Werror=uninitialized -Werror=return-type -Werror=vla -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion ``` **`-Wconversion` is strict**: every implicit narrowing conversion needs an explicit cast. --- ## Commit messages Format: ``` [ (Platform)]: ``` Scope is the module or subsystem name; Platform is optional. Use the third-person singular present tense, capitalize the first letter, no trailing period. Real examples from recent history: ``` Processes (Haiku): honors `options->countKprocs` WM (macOS): improves reliability of WM plugin detection Memory (Windows): prefers `NQSI` Top (Linux): improves performance of `stat` parsing Logo (Builtin): adds omarchy Global: introduces global macro `FF_PATH_PKG_BASE` to replace `_PATH_LOCALBASE` LM (OpenBSD): adds support CI: disables fail on alert Doc: updates README Presets: moves `top` to the bottom of running modules [ci skip] ``` Common verbs: `adds`, `removes`, `fixes`, `improves`, `updates`, `corrects`, `prefers`, `honors`, `disables`, `enables`, `introduces`, `detects`, `reports`, `skips`, `uses`. Scopes in use: `Top`, `Processes`, `Memory`, `Logo (Builtin)`, `CI`, `Doc`, `Presets`, `Global`, plus individual module names. Documentation-only changes get a `[ci skip]` suffix. --- ## Changelog User-visible changes go into `CHANGELOG.md`, under the topmost version heading: ```markdown # Unreleased Changes: * The DE / WM / LM modules now reports the full name ... Features: * Added Top module to print processes with the highest CPU, memory or disk I/O usage. (Top) * Improved Wi-Fi module * Added Wifi channel width detection, exposed via `{channel-width}` in custom format. * Improved Wifi channel frequency accuracy on Windows, macOS. * Added Battery detection support on SunOS. (Battery, SunOS) Bugfixes: * Fixed I/O rate calculation precision in DiskIO and NetIO. (DiskIO / NetIO) ``` Rules: - Three sections: `Changes:` (behavior changes), `Features:`, `Bugfixes:` - Past tense: `Added` / `Fixed` / `Improved` - End each entry with its scope: `(Module, Platform)` or `(ModuleA / ModuleB)` - Sub-bullets are indented 4 spaces --- ## Tests Tests are standalone executables in `tests/` using a `VERIFY` macro that exits non-zero on failure: ```c #define VERIFY(expression) \ if (!(expression)) testFailed(&strbuf, #expression, __LINE__) ``` Current tests: `strbuf.c`, `list.c`, `format.c`, `color.c`, `duration.c`, `strutil.c`. ```sh cmake -B build -DBUILD_TESTS=On cmake --build build cd build && ctest --output-on-failure ``` Coverage focuses on the core data structures and the formatting engine in `common/`. The `detection/` layer has no automated tests (it depends too heavily on a real system) and is instead covered by the CI matrix — 20 workflows under `.github/workflows/` spanning Linux (including musl, loong64, armv7l, i686), macOS, Windows, FreeBSD, NetBSD, OpenBSD, DragonFly, Solaris, OmniOS and Haiku, plus spellcheck and benchmark jobs. **If you touch `common/FFstrbuf.h`, `common/format.h` or `common/color.h`, please extend the matching test.** --- ## Pull requests 1. **Open an issue first** (feature request / bug report / logo request) so you don't waste effort. Templates are in `.github/ISSUE_TEMPLATE/`. 2. Branch off `dev` — **`dev` is the main development branch**, not `master`. 3. Follow the [commit message convention](#commit-messages). 4. Update `CHANGELOG.md` for user-visible changes. 5. Open the PR against `dev` and fill in `.github/pull_request_template.md`: - Summary - Related issue (**required for new logos**, otherwise the PR won't be accepted) - Changes - Screenshots (required for visual changes) - Checklist: confirm you tested locally ### Pre-submit checklist ```sh clang-format -i # format codespell # spelling cmake -B build -DBUILD_TESTS=On && cmake --build build -j cd build && ctest --output-on-failure # tests ./build/fastfetch --format json # verify JSON output is well-formed ./build/fastfetch -c presets/all.jsonc --stat false # smoke-test every module ``` --- ## Gotchas Things that are easy to get wrong when reading this codebase. Most of them follow from the "obsessively fast startup" goal. ### 1. Option structs must not exceed 256 bytes `FF_OPTION_MAX_SIZE = 1 << 8`. Exceeding it is caught at compile time by `static_assert`. Don't try to raise the value — it determines the stack cost of every module invocation. ### 2. `FF_MODULE_DISABLE_*` controls registration, not compilation ```c // verbatim from the top of modules/modules.c: // FF_MODULE_DISABLE_ only controls if the module is registered, // the module code itself is still compiled. // We rely `LTO` to remove the unused code (only enabled in Release mode) ``` **Disabling modules in a Debug build does not shrink the binary.** LTO is enabled whenever `CMAKE_BUILD_TYPE != Debug`, and the default `RelWithDebInfo` already satisfies that — so measure size with `RelWithDebInfo` or `Release`, never with `Debug`. (The "Release mode" wording in the comment above is imprecise.) ### 3. CMake globs don't trigger reconfiguration Both the module directories and the logo files are discovered by glob. CMake will not notice new files on its own — you need to re-run `cmake -B build` (or delete `build/CMakeCache.txt` and start over). This is the classic CMake footgun. ### 4. CLI module options are removed Per-module command-line flags such as `--cpu-temp` are **no longer supported**. The only job of `ffParseModuleOptions` now is to translate the flag into a JSON key, then `exit(477)` with a pointer to the config file: ``` Error: Unsupported module option: --cpu-temp Support of module options has been removed. Please add the flag to the JSON config instead. Example (demonstration only): `{ "modules": [ { "type": "cpu", "temp": true } ] }` ``` **JSONC is the first-class configuration interface; the command line is not.** When adding module options, implement `parseJsonObject` only — do not add a CLI branch. ### 5. `defaultOrder = 0` hides a module from `--gen-config` `collectModuleInfos` (`genconfig.c:186`) skips any module whose `defaultOrder` is `0`. C zero-initializes the field, so **omitting it is the same as setting it to 0**. Only `logo`, `command` and `custom` do this on purpose. `defaultOrder` affects only the ordering in the interactive `--gen-config` picker; it has no effect on runtime output order. ### 6. Localization uses byte offsets, not enums `instance.config.display.keyLanguage` does not hold a language enum — it holds a **byte offset** such as `offsetof(FFModuleDisplayName, zh_CN)`. The macro `FF_MODULE_GET_DISPLAY_NAME` (`common/option.h:123`) does plain pointer arithmetic with it: ```c #define FF_MODULE_GET_DISPLAY_NAME(moduleName) \ (*(const char**) ((uint8_t*) &ff ## moduleName ## ModuleInfo.displayName + instance.config.display.keyLanguage)) ``` Consequence: **the field order of `FFModuleDisplayName` must never change** — reordering silently scrambles every language. ### 7. The `multithreading` switch barely does anything The global `multithreading` option currently takes effect in exactly one place: `common/impl/networking_linux.c:339`. Modules are still printed sequentially. Modules that need concurrency go through the `ffPrepare*` warm-up hooks instead, which start sampling or fire off requests before the print loop begins. ### 8. Don't print or read config inside `detection/` The `detection/` layer must stay pure: read system state, fill a struct, return an error string. Any `printf` or any read of `instance.config` there is a design error. ### 9. `src/logo/builtin.c` and `3rdparty/` are formatting-exempt The former is a large generated/hand-maintained data table, the latter is upstream code. Both are excluded via `.clang-format-ignore` — leave them alone. --- ## Reference - Configuration guide: - JSON schema: `doc/json_schema.json` - Example presets: `presets/` (`all.jsonc`, `neofetch.jsonc`, `ci.jsonc`, …) - Man page source: `doc/fastfetch.1.in` - Code generation scripts: `scripts/` (`gen-pciids.py`, `gen-amdgpuids.py`, `gen-man.py`)