mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-12 09:58:03 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b013377e5 | |||
| 66e9e38f28 | |||
| 5d290cc3d5 | |||
| 2e542bea64 | |||
| c708b6f5cd | |||
| 6a275da8b1 | |||
| ecee5694fb | |||
| 4e78f1b703 | |||
| 62f78ca60c | |||
| 00af2d1df2 | |||
| 1128a75d8c | |||
| ce61783c4b | |||
| 20597bff00 | |||
| caf64f5e4d | |||
| 90a6e90b19 | |||
| e9f719f37a | |||
| 8c98bf1953 | |||
| 1c1136ebd1 | |||
| 2787d97973 | |||
| 2e06a73102 | |||
| 37c198d997 | |||
| 37afe5a79d |
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: inputs.arch == 'amd64'
|
||||
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
with:
|
||||
languages: c
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
- name: perform CodeQL analysis
|
||||
if: inputs.arch == 'amd64'
|
||||
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
|
||||
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
|
||||
- name: list features
|
||||
run: ./fastfetch --list-features
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: cmake --version
|
||||
|
||||
- name: configure project
|
||||
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
|
||||
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_VERSION=5.10.0 -DCMAKE_SYSTEM_PROCESSOR=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
|
||||
|
||||
- name: build project
|
||||
run: cmake --build . --target package --verbose -j4
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: run VM
|
||||
uses: vmactions/solaris-vm@315163f088b66e55bbcc45928bd224d4973b2312 # v1.3.8
|
||||
uses: vmactions/solaris-vm@96d8d976f9e67d82ec6c7e8ce9c1060731f9e21c # v1.3.9
|
||||
with:
|
||||
usesh: true
|
||||
envs: 'CMAKE_BUILD_TYPE'
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
# 2.68.0
|
||||
# 2.68.1
|
||||
|
||||
Changes:
|
||||
* The DE / WM / LM modules now reports the full name "Desktop Environment" / "Window Manager" / "Login Manager" instead of the abbreviations.
|
||||
@@ -11,6 +11,7 @@ Features:
|
||||
* Added module key localization support. (General)
|
||||
* Added a new option `--key-language <?lang>` (`display.key.language: "<?lang>"` in JSON config). When left empty, it defaults to the system locale.
|
||||
* See `fastfetch -h key-language` for all supported languages.
|
||||
* Exposed in custom key format as `{module-name}`.
|
||||
* Improved Wi-Fi module
|
||||
* Added Wifi channel width detection, exposed via `{channel-width}` in custom format (reports 0 when not supported).
|
||||
* Improved Wifi channel frequency accuracy and fortified detection on Windows, macOS.
|
||||
@@ -29,6 +30,8 @@ Bugfixes:
|
||||
* Fixed memory usage detection support on x86-32 FreeBSD (Memory, FreeBSD)
|
||||
* Note: Although 32-bit systems are still supported, they are deprecated and barely tested. Users are encouraged to upgrade to 64-bit systems.
|
||||
* Some internal cleanups and optimizations.
|
||||
* Attempted to fix potential issues on big-endian systems
|
||||
* Not tested due to lack of access to big-endian hardware.
|
||||
|
||||
Logos:
|
||||
* Added macOS_old
|
||||
|
||||
+27
-91
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD
|
||||
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
|
||||
|
||||
project(fastfetch
|
||||
VERSION 2.68.0
|
||||
VERSION 2.68.1
|
||||
LANGUAGES C
|
||||
DESCRIPTION "Fast neofetch-like system information tool"
|
||||
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
|
||||
@@ -9,9 +9,6 @@ project(fastfetch
|
||||
|
||||
set(PROJECT_LICENSE "MIT license")
|
||||
|
||||
if(DEFINED CMAKE_SYSTEM_PROCESSOR_OVERRIDE) # Used by github actions for i686 build
|
||||
set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR_OVERRIDE} CACHE INTERNAL "")
|
||||
endif()
|
||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR)
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
set(CMAKE_SYSTEM_PROCESSOR "amd64")
|
||||
@@ -128,13 +125,24 @@ if(APPLE)
|
||||
option(ENABLE_APPLE_MEMSIZE_USABLE "Use usable memory size as total memory size in Memory module, to match other systems" OFF)
|
||||
endif()
|
||||
|
||||
file(GLOB FF_MODULE_DIRS CONFIGURE_DEPENDS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/*/")
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/${FF_MODULE_DIR}")
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
|
||||
# A module is a directory `src/modules/<module>/` that contains `<module>.c`.
|
||||
# Globbing the sources rather than the directories skips empty directories and
|
||||
# other leftovers, and makes `CONFIGURE_DEPENDS` react to added or removed sources.
|
||||
file(GLOB FF_MODULE_SRCS CONFIGURE_DEPENDS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/*/*.c")
|
||||
set(FF_MODULE_DIRS "")
|
||||
foreach(FF_MODULE_SRC ${FF_MODULE_SRCS})
|
||||
get_filename_component(FF_MODULE_DIR "${FF_MODULE_SRC}" DIRECTORY)
|
||||
get_filename_component(FF_MODULE_NAME "${FF_MODULE_SRC}" NAME_WE)
|
||||
if("${FF_MODULE_DIR}" STREQUAL "${FF_MODULE_NAME}")
|
||||
list(APPEND FF_MODULE_DIRS "${FF_MODULE_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(FF_MODULE_SRCS)
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
|
||||
endforeach()
|
||||
|
||||
set(BINARY_LINK_TYPE_OPTIONS dlopen dynamic static)
|
||||
set(BINARY_LINK_TYPE dlopen CACHE STRING "How to link fastfetch")
|
||||
@@ -496,88 +504,18 @@ set(LIBFASTFETCH_SRC
|
||||
src/logo/image/im7.c
|
||||
src/logo/image/image.c
|
||||
src/logo/logo.c
|
||||
src/modules/battery/battery.c
|
||||
src/modules/bios/bios.c
|
||||
src/modules/bluetooth/bluetooth.c
|
||||
src/modules/bluetoothradio/bluetoothradio.c
|
||||
src/modules/board/board.c
|
||||
src/modules/bootmgr/bootmgr.c
|
||||
src/modules/brightness/brightness.c
|
||||
src/modules/break/break.c
|
||||
src/modules/btrfs/btrfs.c
|
||||
src/modules/camera/camera.c
|
||||
src/modules/chassis/chassis.c
|
||||
src/modules/codec/codec.c
|
||||
src/modules/colors/colors.c
|
||||
src/modules/cpu/cpu.c
|
||||
src/modules/cpucache/cpucache.c
|
||||
src/modules/cpuusage/cpuusage.c
|
||||
src/modules/cursor/cursor.c
|
||||
src/modules/custom/custom.c
|
||||
src/modules/command/command.c
|
||||
src/modules/datetime/datetime.c
|
||||
src/modules/de/de.c
|
||||
src/modules/disk/disk.c
|
||||
src/modules/diskio/diskio.c
|
||||
src/modules/dns/dns.c
|
||||
src/modules/editor/editor.c
|
||||
src/modules/font/font.c
|
||||
src/modules/gpu/gpu.c
|
||||
src/modules/host/host.c
|
||||
src/modules/icons/icons.c
|
||||
src/modules/initsystem/initsystem.c
|
||||
src/modules/gamepad/gamepad.c
|
||||
src/modules/kernel/kernel.c
|
||||
src/modules/keyboard/keyboard.c
|
||||
src/modules/lm/lm.c
|
||||
src/modules/loadavg/loadavg.c
|
||||
src/modules/locale/locale.c
|
||||
src/modules/localip/localip.c
|
||||
src/modules/logo/logo.c
|
||||
src/modules/memory/memory.c
|
||||
src/modules/monitor/monitor.c
|
||||
src/modules/netio/netio.c
|
||||
src/modules/opencl/opencl.c
|
||||
src/modules/opengl/opengl.c
|
||||
src/modules/os/os.c
|
||||
src/modules/packages/packages.c
|
||||
src/modules/physicaldisk/physicaldisk.c
|
||||
src/modules/physicalmemory/physicalmemory.c
|
||||
src/modules/processes/processes.c
|
||||
src/modules/player/player.c
|
||||
src/modules/poweradapter/poweradapter.c
|
||||
src/modules/publicip/publicip.c
|
||||
src/modules/display/display.c
|
||||
src/modules/separator/separator.c
|
||||
src/modules/shell/shell.c
|
||||
src/modules/sound/sound.c
|
||||
src/modules/swap/swap.c
|
||||
src/modules/media/media.c
|
||||
src/modules/mouse/mouse.c
|
||||
src/modules/terminal/terminal.c
|
||||
src/modules/terminaltheme/terminaltheme.c
|
||||
src/modules/terminalfont/terminalfont.c
|
||||
src/modules/terminalsize/terminalsize.c
|
||||
src/modules/theme/theme.c
|
||||
src/modules/title/title.c
|
||||
src/modules/top/top.c
|
||||
src/modules/tpm/tpm.c
|
||||
src/modules/uptime/uptime.c
|
||||
src/modules/users/users.c
|
||||
src/modules/version/version.c
|
||||
src/modules/vulkan/vulkan.c
|
||||
src/modules/wallpaper/wallpaper.c
|
||||
src/modules/weather/weather.c
|
||||
src/modules/wifi/wifi.c
|
||||
src/modules/wm/wm.c
|
||||
src/modules/wmtheme/wmtheme.c
|
||||
src/modules/zpool/zpool.c
|
||||
src/modules/modules.c
|
||||
src/options/display.c
|
||||
src/options/logo.c
|
||||
src/options/general.c
|
||||
)
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/modules/${FF_MODULE_DIR}/${FF_MODULE_DIR}.c
|
||||
)
|
||||
endforeach()
|
||||
|
||||
if(LINUX)
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/common/impl/dbus.c
|
||||
@@ -1485,11 +1423,9 @@ add_library(libfastfetch OBJECT
|
||||
)
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/${FF_MODULE_DIR}")
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
if(MODULE_DISABLE_${FF_MODULE_UPPER})
|
||||
target_compile_definitions(libfastfetch PUBLIC FF_MODULE_DISABLE_${FF_MODULE_UPPER}=1)
|
||||
endif()
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
if(MODULE_DISABLE_${FF_MODULE_UPPER})
|
||||
target_compile_definitions(libfastfetch PUBLIC FF_MODULE_DISABLE_${FF_MODULE_UPPER}=1)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
||||
+144
-71
@@ -2,15 +2,15 @@
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Note
|
||||
Disclaimer
|
||||
</summary>
|
||||
|
||||
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.
|
||||
This document is generated by AI and reviewed by humans. It is primarily intended for AI-assisted development, while remaining useful to human developers.
|
||||
</details>
|
||||
|
||||
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.
|
||||
Thank you 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.
|
||||
fastfetch is a system information tool written in C23, supporting Linux, macOS, Windows, the BSDs, Solaris, Haiku and Android. The project places strong emphasis on **startup time** and on keeping **dependencies optional**; many design decisions only make sense under that constraint, and this document returns to it repeatedly.
|
||||
|
||||
---
|
||||
|
||||
@@ -29,7 +29,7 @@ fastfetch is a system information tool written in C23, supporting Linux, macOS,
|
||||
- [Changelog](#changelog)
|
||||
- [Tests](#tests)
|
||||
- [Pull requests](#pull-requests)
|
||||
- [Gotchas](#gotchas)
|
||||
- [Pitfalls](#pitfalls)
|
||||
|
||||
---
|
||||
|
||||
@@ -38,14 +38,14 @@ fastfetch is a system information tool written in C23, supporting Linux, macOS,
|
||||
| Task | Start here | Verify with |
|
||||
|---|---|---|
|
||||
| Build and run fastfetch | `run.sh`, `CMakeLists.txt` | `./run.sh` |
|
||||
| Add a module | `src/modules/<name>/`, `src/detection/<name>/`, `src/modules/modules.c` | `cmake -B build && cmake --build build -j` |
|
||||
| Add a platform implementation | `src/detection/<name>/<name>_<platform>.c` | Build on the target platform or CI |
|
||||
| Add a module | `src/modules/<name>/`, `src/detection/<name>/`, `src/modules/modules.c`, `CMakeLists.txt` | `cmake -B build && cmake --build build -j` |
|
||||
| Add a platform implementation | `src/detection/<name>/<name>_<platform>.c`, the matching platform block in `CMakeLists.txt` | Build on the target platform or CI |
|
||||
| Add an ASCII logo | `src/logo/ascii/<letter>/<name>.txt`, matching `<letter>.inc` | `./build/fastfetch --logo <name>` |
|
||||
| Change formatting or JSON output | `src/modules/<name>/<name>.c` | `./build/fastfetch -s <name> --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.
|
||||
For a typical code change, the shortest useful iteration 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 that CMake refreshes its file globs.
|
||||
|
||||
---
|
||||
|
||||
@@ -53,7 +53,7 @@ For a normal code change, the shortest useful loop is: configure, build the `fas
|
||||
|
||||
### Building
|
||||
|
||||
The quickest way is `run.sh` in the repository root — it creates `build/`, configures, compiles and runs the binary:
|
||||
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
|
||||
@@ -68,7 +68,7 @@ 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).
|
||||
The default build type is `RelWithDebInfo`. LTO is enabled whenever `ENABLE_LTO=ON` **and** `CMAKE_BUILD_TYPE != Debug`, so the default configuration enables it and only `Debug` builds omit it. This is significant because LTO here is not merely an optimization: it is what removes the code of disabled modules. See [Pitfalls](#pitfalls).
|
||||
|
||||
### Common build options
|
||||
|
||||
@@ -123,12 +123,12 @@ fastfetch.c → options/ (global configuration)
|
||||
→ 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:
|
||||
The number of module directories does not match the number of detection directories, which 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`).
|
||||
- **13 modules have no detection directory** — they are either pure layout (`break`, `separator`, `colors`, `title`, `logo`) or reuse another module'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/<x>/` always has a matching `detection/<x>/`.
|
||||
Therefore, do not assume that `modules/<x>/` always has a matching `detection/<x>/`.
|
||||
|
||||
### Layer boundaries
|
||||
|
||||
@@ -139,13 +139,13 @@ So do not assume `modules/<x>/` always has a matching `detection/<x>/`.
|
||||
| `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__`.
|
||||
A simple check: **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:
|
||||
This is the most important convention in the codebase. Every feature consists of a fixed set of files; using CPU as the example:
|
||||
|
||||
```
|
||||
modules/cpu/option.h module option struct
|
||||
@@ -158,7 +158,7 @@ detection/cpu/cpu_bsd.c │
|
||||
detection/cpu/cpu_nosupport.c ┘
|
||||
```
|
||||
|
||||
The interface is deliberately minimal — usually just two things:
|
||||
The interface is deliberately minimal, typically consisting of only two declarations:
|
||||
|
||||
```c
|
||||
typedef struct FFCPUResult { ... } FFCPUResult; // result struct
|
||||
@@ -169,7 +169,7 @@ The `const char*` return value is an **error string**; `nullptr` means success.
|
||||
|
||||
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/`.
|
||||
**The benefit of this separation:** adding a platform never touches `modules/`; fixing output formatting never touches `detection/`.
|
||||
|
||||
---
|
||||
|
||||
@@ -197,7 +197,7 @@ typedef struct FFModuleBaseInfo {
|
||||
} 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 source comment acknowledges that this is undefined behavior, since `void*` is not compatible with `FF*Options*`. It is a pragmatic compromise to obtain polymorphism in C; do not attempt to "fix" it.
|
||||
|
||||
### The registry: a first-letter hash bucket
|
||||
|
||||
@@ -210,7 +210,7 @@ for (; *modules; ++modules) { // linear s
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
Because the registry is small, a single arithmetic bucket lookup followed by 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`, and keep the existing `nullptr` terminator at the end.
|
||||
|
||||
### The calling convention: zero heap allocation
|
||||
|
||||
@@ -230,7 +230,7 @@ baseInfo->destroyOptions(optionBuf);
|
||||
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.
|
||||
**In other words, no module option struct may exceed 256 bytes.** This is a hard constraint, enforced at compile time.
|
||||
|
||||
The three dispatch entry points:
|
||||
|
||||
@@ -238,20 +238,49 @@ The three dispatch entry points:
|
||||
|---|---|---|
|
||||
| `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_<UPPER>` option per directory:
|
||||
`CMakeLists.txt:134` globs `src/modules/*/*.c`, keeps only the entries whose file name matches their directory name, and generates a `MODULE_DISABLE_<UPPER>` option for each:
|
||||
|
||||
```cmake
|
||||
file(GLOB FF_MODULE_DIRS RELATIVE "..." ".../src/modules/*/")
|
||||
file(GLOB FF_MODULE_SRCS CONFIGURE_DEPENDS RELATIVE "..." ".../src/modules/*/*.c")
|
||||
set(FF_MODULE_DIRS "")
|
||||
foreach(FF_MODULE_SRC ${FF_MODULE_SRCS})
|
||||
get_filename_component(FF_MODULE_DIR "${FF_MODULE_SRC}" DIRECTORY)
|
||||
get_filename_component(FF_MODULE_NAME "${FF_MODULE_SRC}" NAME_WE)
|
||||
if("${FF_MODULE_DIR}" STREQUAL "${FF_MODULE_NAME}")
|
||||
list(APPEND FF_MODULE_DIRS "${FF_MODULE_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
|
||||
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`).
|
||||
Because the glob matches sources rather than directories, a directory counts as a module only when `src/modules/<name>/<name>.c` exists. Empty directories — which git does not track, so they are easy to leave behind — and stray files are ignored instead of breaking the configure step with `Cannot find source file`.
|
||||
|
||||
### Module sources are discovered automatically
|
||||
|
||||
`FF_MODULE_DIRS` also drives the source list (`CMakeLists.txt:516`):
|
||||
|
||||
```cmake
|
||||
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
src/modules/${FF_MODULE_DIR}/${FF_MODULE_DIR}.c
|
||||
)
|
||||
endforeach()
|
||||
```
|
||||
|
||||
Consequences:
|
||||
|
||||
- `src/modules/<name>/<name>.c` is compiled as soon as the file exists — **there is no source list to edit for the module layer**.
|
||||
- The file name must match the directory name. A directory whose `.c` file is named differently, or has none at all, is silently not a module: a typo therefore shows up as a module missing from `fastfetch --list-modules`, not as a build error.
|
||||
- The glob uses `CONFIGURE_DEPENDS`, so with the Makefile and Ninja generators the build re-evaluates it and re-runs CMake when a module source is added or removed. With other generators, or to be safe, re-run `cmake -B build` explicitly.
|
||||
|
||||
Only the module layer is automated. Sources under `src/detection/` and `src/common/impl/` are **not** globbed and must still be listed by hand — see [step 5](#5-add-the-platform-sources-to-cmakeliststxt).
|
||||
|
||||
---
|
||||
|
||||
@@ -276,9 +305,7 @@ typedef struct 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)).
|
||||
`FFModuleArgs` must come first. It provides `key`, `format`, `outputColor`, `keyColor`, `keyIcon` and `keyWidth`; because it is the first field, `ffJsonConfigParseModuleArgs()` handles these generically, **so no parsing code is required**.
|
||||
|
||||
### 2. Implement detection
|
||||
|
||||
@@ -307,7 +334,7 @@ const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result) {
|
||||
}
|
||||
```
|
||||
|
||||
**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.
|
||||
**Write one file per target platform, plus a `foo_nosupport.c` fallback for the remaining platforms.** The repository currently contains 45 `*_nosupport.c` files serving this purpose.
|
||||
|
||||
### 3. Implement the module
|
||||
|
||||
@@ -339,11 +366,11 @@ FFModuleBaseInfo ffFooModuleInfo = {
|
||||
|
||||
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.**
|
||||
- **`displayName`** is a literal block of all 20 language fields; there is no shortcut macro, so copy the layout from an existing 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 [Pitfalls](#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`; **any placeholder omitted here is neither listed nor usable by 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.
|
||||
- **`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`, excludes the module from the interactive `--gen-config` picker. Only `logo`, `command` and `custom` rely on this behavior, because they require user arguments or are invoked directly by the display layer.
|
||||
|
||||
### 4. Register it
|
||||
|
||||
@@ -358,11 +385,46 @@ Points to note:
|
||||
|
||||
`FF_MODULE_DISABLE_FOO` is generated by CMake — you do not define it yourself.
|
||||
|
||||
### 5. Modules that need warm-up
|
||||
### 5. Add the platform sources to `CMakeLists.txt`
|
||||
|
||||
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.
|
||||
The module layer is discovered by glob (see [Build-time module discovery](#module-sources-are-discovered-automatically)), **but `src/detection/**` and `src/common/impl/**` are not**. `CMakeLists.txt` lists them explicitly, inside one mutually exclusive chain of platform blocks:
|
||||
|
||||
### 6. Verify
|
||||
| Block | Line | Covers |
|
||||
|---|---|---|
|
||||
| `if(LINUX)` | `CMakeLists.txt:522` | Linux |
|
||||
| `elseif(ANDROID)` | `CMakeLists.txt:608` | Android (Termux) |
|
||||
| `elseif(FreeBSD)` | `CMakeLists.txt:691` | FreeBSD, MidnightBSD, DragonFly |
|
||||
| `elseif(NetBSD)` | `CMakeLists.txt:788` | NetBSD |
|
||||
| `elseif(OpenBSD)` | `CMakeLists.txt:872` | OpenBSD |
|
||||
| `elseif(APPLE)` | `CMakeLists.txt:959` | macOS / iOS |
|
||||
| `elseif(WIN32)` | `CMakeLists.txt:1046` | Windows |
|
||||
| `elseif(SunOS)` | `CMakeLists.txt:1121` | Solaris / illumos |
|
||||
| `elseif(Haiku)` | `CMakeLists.txt:1204` | Haiku |
|
||||
| `elseif(GNU)` | `CMakeLists.txt:1282` | GNU/Hurd |
|
||||
|
||||
Add the platform implementation to every block whose platform it supports, and `foo_nosupport.c` to every remaining block, so that all ten platforms still link:
|
||||
|
||||
```cmake
|
||||
elseif(FreeBSD)
|
||||
list(APPEND LIBFASTFETCH_SRC
|
||||
...
|
||||
src/detection/foo/foo_bsd.c
|
||||
)
|
||||
```
|
||||
|
||||
Points to note:
|
||||
|
||||
- Exactly one block is compiled per build, so a file omitted from a block does not exist for that platform, and the link fails with an undefined reference to `ffDetectFoo()` — **on that platform only**. A missing entry therefore builds fine locally and fails in CI; this is why every block must be covered.
|
||||
- A platform may reuse another platform's implementation instead of a stub. `src/common/impl/networking_linux.c`, for example, is listed in nine of the ten blocks (all but `WIN32`). Check where the closest sibling module points before adding a new file.
|
||||
- `DragonFly` is handled by the inner `if(DragonFly)` sub-block inside the FreeBSD block (`CMakeLists.txt:773`); add the variant there, as `processes`, `top` and `wifi` do.
|
||||
- New helpers under `src/common/impl/` follow the same rule: they are not globbed, and each block that needs one must list it.
|
||||
- A few files are appended outside the platform chain because they depend on an option or on a specific feature — for example the proprietary GPU backends (`CMakeLists.txt:1379`) and `src/common/impl/wcwidth.c` (`CMakeLists.txt:1408`). Those are written by hand as well.
|
||||
|
||||
### 6. 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 matching first-letter `case`. Six modules currently do this: CPUUsage, DiskIO, NetIO, PublicIP, Top and Weather.
|
||||
|
||||
### 7. Verify
|
||||
|
||||
```sh
|
||||
cmake -B build && cmake --build build -j
|
||||
@@ -371,7 +433,9 @@ cmake -B build && cmake --build build -j
|
||||
./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`.
|
||||
Note the `-format` suffix on the help flag: `fastfetch -h foo` is not supported; only `fastfetch -h foo-format` works.
|
||||
|
||||
If you added detection sources, re-check [step 5](#5-add-the-platform-sources-to-cmakeliststxt) before pushing: a platform block you missed compiles fine locally and fails only when that platform is built.
|
||||
|
||||
---
|
||||
|
||||
@@ -379,13 +443,13 @@ Note the `-format` suffix on the help flag: `fastfetch -h foo` does not work, on
|
||||
|
||||
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
|
||||
### 1. Add the ASCII file
|
||||
|
||||
```
|
||||
src/logo/ascii/<first-letter>/<distro>.txt
|
||||
```
|
||||
|
||||
For example `src/logo/ascii/o/omarchy.txt`. Directories are already split by first letter (`a/` … `z/`, plus `_/`).
|
||||
For example `src/logo/ascii/d/distro.txt`. Directories are already split by first letter (`a/` … `z/`, plus `_/`).
|
||||
|
||||
The file is plain ASCII art with `$1` … `$9` as color placeholders:
|
||||
|
||||
@@ -395,26 +459,29 @@ $3 /\\\///
|
||||
$2refined.$1 /\\\\//
|
||||
```
|
||||
|
||||
- `$1` … `$9` map to palette slots 1–9; at most 9 (`FASTFETCH_LOGO_MAX_COLORS = 9`)
|
||||
- `$1` … `$9` map to palette slots 1–9; at most 9 are supported (`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
|
||||
- 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**, as 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_<UPPERCASE>` macro (`CMakeLists.txt:418`), but **the registry itself is maintained by hand**. Edit `src/logo/ascii/<first-letter>.inc`:
|
||||
CMake turns each `.txt` file into a `FASTFETCH_DATATEXT_LOGO_<UPPERCASE>` macro (`CMakeLists.txt:418`), but **the registry itself is maintained by hand**. Edit `src/logo/ascii/<first-letter>.inc`:
|
||||
|
||||
```c
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_OMARCHY
|
||||
// Omarchy
|
||||
// src/logo/ascii/d.inc
|
||||
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_DISTRO
|
||||
// Distro
|
||||
{
|
||||
.names = { "omarchy", "Omarchy" },
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_OMARCHY,
|
||||
.names = { "Distro" }, // ID (preferred) or NAME from /etc/os-release, do NOT add both
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_DISTRO,
|
||||
.colors = {
|
||||
FF_COLOR_FG_BLUE,
|
||||
FF_COLOR_FG_WHITE,
|
||||
FF_COLOR_FG_CYAN,
|
||||
FF_COLOR_FG_PRIMARY, // recommended for using as WHITE replacement, light-theme terminal friendly
|
||||
FF_COLOR_FG_BLUE, // preferred
|
||||
FF_COLOR_FG_256 "34",
|
||||
FF_COLOR_FG_RGB "0;255;0", // not recommended because of bad compatibility with raw TTY
|
||||
},
|
||||
},
|
||||
#endif
|
||||
@@ -431,13 +498,13 @@ If the same OS has multiple logo variants, mark the variant explicitly with `.ty
|
||||
.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 <source>`.
|
||||
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 `-l <source>`.
|
||||
|
||||
### 3. Verify
|
||||
|
||||
```sh
|
||||
./build/fastfetch --logo omarchy
|
||||
./build/fastfetch --list-logos | grep -i omarchy
|
||||
./build/fastfetch -l distro
|
||||
./build/fastfetch --list-logos | grep -i distro
|
||||
```
|
||||
|
||||
---
|
||||
@@ -474,9 +541,9 @@ Current distribution of platform files in `detection/`:
|
||||
|
||||
(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.
|
||||
**Do not accumulate `#ifdef __linux__` blocks in a single 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.
|
||||
`common/` follows the same convention: `common/impl/` contains `io_unix.c` / `io_windows.c`, `netif_linux.c` / `netif_apple.c` / `netif_bsd.c` and similar files, 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.
|
||||
|
||||
@@ -507,7 +574,7 @@ 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).
|
||||
`.editorconfig` specifies LF line endings, a 4-space indent, a final newline, and trailing whitespace trimmed (except in Markdown).
|
||||
|
||||
### Naming
|
||||
|
||||
@@ -522,7 +589,7 @@ clang-format -i src/modules/foo/*.c src/modules/foo/*.h
|
||||
|
||||
### 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.
|
||||
CI runs codespell (`.codespellrc`). Known false positives are listed in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code.
|
||||
|
||||
### Compiler warnings
|
||||
|
||||
@@ -566,7 +633,7 @@ Common verbs: `adds`, `removes`, `fixes`, `improves`, `updates`, `corrects`, `pr
|
||||
|
||||
Scopes in use: `Top`, `Processes`, `Memory`, `Logo (Builtin)`, `CI`, `Doc`, `Presets`, `Global`, plus individual module names.
|
||||
|
||||
Documentation-only changes get a `[ci skip]` suffix.
|
||||
Documentation-only changes use a `[ci skip]` suffix.
|
||||
|
||||
---
|
||||
|
||||
@@ -617,21 +684,21 @@ 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.
|
||||
Coverage focuses on the core data structures and the formatting engine in `common/`. The `detection/` layer has no automated tests, because it depends on the state of a running system; it 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.**
|
||||
**If you modify `common/FFstrbuf.h`, `common/format.h` or `common/color.h`, extend the corresponding 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/`.
|
||||
1. **Open an issue first** (feature request / bug report / logo request) to confirm that the change is wanted before investing 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)
|
||||
- Related issue (**required for new logos**; otherwise the PR is not accepted)
|
||||
- Changes
|
||||
- Screenshots (required for visual changes)
|
||||
- Checklist: confirm you tested locally
|
||||
@@ -649,13 +716,13 @@ cd build && ctest --output-on-failure # tests
|
||||
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
## Pitfalls
|
||||
|
||||
Things that are easy to get wrong when reading this codebase. Most of them follow from the "obsessively fast startup" goal.
|
||||
The following points are easy to misinterpret when reading this codebase. Most of them follow from the startup-time objective described above.
|
||||
|
||||
### 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.
|
||||
`FF_OPTION_MAX_SIZE = 1 << 8`. Exceeding it is caught at compile time by `static_assert`. Do not attempt to increase the value: it determines the stack cost of every module invocation.
|
||||
|
||||
### 2. `FF_MODULE_DISABLE_*` controls registration, not compilation
|
||||
|
||||
@@ -668,9 +735,11 @@ Things that are easy to get wrong when reading this codebase. Most of them follo
|
||||
|
||||
**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
|
||||
### 3. Only two things are discovered by glob
|
||||
|
||||
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.
|
||||
The module sources (`CMakeLists.txt:134`) and the logo `.txt` files (`CMakeLists.txt:429`). Both use `CONFIGURE_DEPENDS`, so with the Makefile and Ninja generators the build re-checks the glob and re-runs CMake when the result changes; other generators (Visual Studio and Xcode in particular) do not track it as reliably. Re-run `cmake -B build` after adding a module source or a logo file rather than relying on that behavior.
|
||||
|
||||
Everything else — `src/detection/**` and `src/common/impl/**`, and any source outside the platform chain — must be listed in `CMakeLists.txt` by hand; see [step 5](#5-add-the-platform-sources-to-cmakeliststxt).
|
||||
|
||||
### 4. CLI module options are removed
|
||||
|
||||
@@ -686,9 +755,9 @@ Error: Unsupported module option: --cpu-temp
|
||||
|
||||
### 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.
|
||||
`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` rely on this intentionally.
|
||||
|
||||
`defaultOrder` affects only the ordering in the interactive `--gen-config` picker; it has no effect on runtime output order.
|
||||
`defaultOrder` affects only the ordering in the interactive `--gen-config` picker; it has no effect on the runtime output order.
|
||||
|
||||
### 6. Localization uses byte offsets, not enums
|
||||
|
||||
@@ -699,19 +768,23 @@ Error: Unsupported module option: --cpu-temp
|
||||
(*(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.
|
||||
Consequence: **the field order of `FFModuleDisplayName` must never change** — reordering the fields silently corrupts the output for every language.
|
||||
|
||||
### 7. The `multithreading` switch barely does anything
|
||||
### 7. The `multithreading` option has limited effect
|
||||
|
||||
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/`
|
||||
This may change in the future. The main blocking issue is that there are dependencies between different modules.
|
||||
|
||||
### 8. Do not print or read configuration 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.
|
||||
|
||||
Use `FF_DEBUG` (`src/common/debug.h`) for logging.
|
||||
|
||||
### 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.
|
||||
The former is a large generated and hand-maintained data table; the latter is upstream code. Both are excluded via `.clang-format-ignore`; do not reformat either.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Vendored
+12
@@ -1,3 +1,15 @@
|
||||
fastfetch (2.68.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
|
||||
|
||||
* Update to 2.68.1
|
||||
|
||||
-- Carter Li <zhangsongcui@live.cn> Wed, 02 Sep 2026 10:47:51 +0800
|
||||
|
||||
fastfetch (2.68.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
|
||||
|
||||
* Update to 2.68.0
|
||||
|
||||
-- Carter Li <zhangsongcui@live.cn> Tue, 01 Sep 2026 16:34:36 +0800
|
||||
|
||||
fastfetch (2.67.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
|
||||
|
||||
* Update to 2.67.0
|
||||
|
||||
@@ -446,7 +446,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"packagesFormat": {
|
||||
"description": "Output format for the `Packages` module. See Wiki for formatting syntax\n 1. {am-system}: Number of am-system packages\n 2. {am-user}: Number of am-user (aka appman) packages\n 3. {appimage}: Number of appimage packages\n 4. {apk}: Number of apk packages\n 5. {brew}: Number of brew packages\n 6. {brew-cask}: Number of brew-cask packages\n 7. {cards}: Number of cards packages\n 8. {choco}: Number of choco packages\n 9. {dpkg}: Number of dpkg packages\n 10. {emerge}: Number of emerge packages\n 11. {eopkg}: Number of eopkg packages\n 12. {flatpak-system}: Number of flatpak-system app packages\n 13. {flatpak-user}: Number of flatpak-user app packages\n 14. {guix-home}: Number of guix-home packages\n 15. {guix-system}: Number of guix-system packages\n 16. {guix-user}: Number of guix-user packages\n 17. {hpkg-system}: Number of hpkg-system packages\n 18. {hpkg-user}: Number of hpkg-user packages\n 19. {install-release}: Number of install-release packages\n 20. {kiss}: Number of kiss packages\n 21. {linglong}: Number of linglong packages\n 22. {lpkg}: Number of lpkg packages\n 23. {lpkgbuild}: Number of lpkgbuild packages\n 24. {macports}: Number of macports packages\n 25. {mport}: Number of mport packages\n 26. {moss}: Number of moss packages\n 27. {nix-default}: Number of nix-default packages\n 28. {nix-system}: Number of nix-system packages\n 29. {nix-user}: Number of nix-user packages\n 30. {opkg}: Number of opkg packages\n 31. {pacman}: Number of pacman packages\n 32. {pacman-branch}: Pacman branch on manjaro\n 33. {pacstall}: Number of pacstall packages\n 34. {paludis}: Number of paludis packages\n 35. {pisi}: Number of pisi packages\n 36. {pkg}: Number of pkg packages\n 37. {pkgsrc}: Number of pkgsrc packages\n 38. {pkgtool}: Number of pkgtool packages\n 39. {porg}: Number of porg packages\n 40. {rpm}: Number of rpm packages\n 41. {scoop-global}: Number of scoop-global packages\n 42. {scoop-user}: Number of scoop-user packages\n 43. {snap}: Number of snap packages\n 44. {soar}: Number of soar packages\n 45. {sorcery}: Number of sorcery packages\n 46. {winget}: Number of winget packages\n 47. {xbps}: Number of xbps packages\n 48. {brew-all}: Total number of all brew packages\n 49. {flatpak-all}: Total number of all flatpak app packages\n 50. {guix-all}: Total number of all guix packages\n 51. {hpkg-all}: Total number of all hpkg packages\n 52. {nix-all}: Total number of all nix packages\n 53. {all}: Number of all packages",
|
||||
"description": "Output format for the `Packages` module. See Wiki for formatting syntax\n 1. {am-system}: Number of am-system packages\n 2. {am-user}: Number of am-user (aka appman) packages\n 3. {appimage}: Number of appimage packages\n 4. {apk}: Number of apk packages\n 5. {brew}: Number of brew packages\n 6. {brew-cask}: Number of brew-cask packages\n 7. {cards}: Number of cards packages\n 8. {choco}: Number of choco packages\n 9. {crux}: Number of crux packages\n 10. {dpkg}: Number of dpkg packages\n 11. {emerge}: Number of emerge packages\n 12. {eopkg}: Number of eopkg packages\n 13. {flatpak-system}: Number of flatpak-system app packages\n 14. {flatpak-user}: Number of flatpak-user app packages\n 15. {guix-home}: Number of guix-home packages\n 16. {guix-system}: Number of guix-system packages\n 17. {guix-user}: Number of guix-user packages\n 18. {hpkg-system}: Number of hpkg-system packages\n 19. {hpkg-user}: Number of hpkg-user packages\n 20. {install-release}: Number of install-release packages\n 21. {kiss}: Number of kiss packages\n 22. {linglong}: Number of linglong packages\n 23. {lpkg}: Number of lpkg packages\n 24. {lpkgbuild}: Number of lpkgbuild packages\n 25. {macports}: Number of macports packages\n 26. {mport}: Number of mport packages\n 27. {moss}: Number of moss packages\n 28. {nix-default}: Number of nix-default packages\n 29. {nix-system}: Number of nix-system packages\n 30. {nix-user}: Number of nix-user packages\n 31. {opkg}: Number of opkg packages\n 32. {pacman}: Number of pacman packages\n 33. {pacman-branch}: Pacman branch on manjaro\n 34. {pacstall}: Number of pacstall packages\n 35. {paludis}: Number of paludis packages\n 36. {pisi}: Number of pisi packages\n 37. {pkg}: Number of pkg packages\n 38. {pkgsrc}: Number of pkgsrc packages\n 39. {pkgtool}: Number of pkgtool packages\n 40. {porg}: Number of porg packages\n 41. {rpm}: Number of rpm packages\n 42. {scoop-global}: Number of scoop-global packages\n 43. {scoop-user}: Number of scoop-user packages\n 44. {snap}: Number of snap packages\n 45. {soar}: Number of soar packages\n 46. {sorcery}: Number of sorcery packages\n 47. {winget}: Number of winget packages\n 48. {xbps}: Number of xbps packages\n 49. {brew-all}: Total number of all brew packages\n 50. {flatpak-all}: Total number of all flatpak app packages\n 51. {guix-all}: Total number of all guix packages\n 52. {hpkg-all}: Total number of all hpkg packages\n 53. {nix-all}: Total number of all nix packages\n 54. {all}: Number of all packages",
|
||||
"type": "string"
|
||||
},
|
||||
"physicaldiskFormat": {
|
||||
@@ -3763,6 +3763,7 @@
|
||||
"apk",
|
||||
"brew",
|
||||
"choco",
|
||||
"crux",
|
||||
"dpkg",
|
||||
"emerge",
|
||||
"eopkg",
|
||||
|
||||
@@ -70,15 +70,15 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
#define FF_IF_MODULE_MATCH(moduleNameConstant) if (ffStrEqualsIgnCase(moduleType, moduleNameConstant) && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))
|
||||
|
||||
switch (moduleType[0]) {
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
case 'C':
|
||||
case 'c':
|
||||
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
|
||||
ffPrepareCPUUsage();
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_DISKIO
|
||||
#if !FF_MODULE_DISABLE_DISKIO
|
||||
case 'D':
|
||||
case 'd':
|
||||
FF_IF_MODULE_MATCH(ffDiskIOModuleInfo.name) {
|
||||
@@ -87,9 +87,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareDiskIO(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_NETIO
|
||||
#if !FF_MODULE_DISABLE_NETIO
|
||||
case 'N':
|
||||
case 'n':
|
||||
FF_IF_MODULE_MATCH(ffNetIOModuleInfo.name) {
|
||||
@@ -98,9 +98,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareNetIO(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_PUBLICIP
|
||||
#if !FF_MODULE_DISABLE_PUBLICIP
|
||||
case 'P':
|
||||
case 'p':
|
||||
FF_IF_MODULE_MATCH(ffPublicIPModuleInfo.name) {
|
||||
@@ -109,9 +109,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPreparePublicIp(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_TOP
|
||||
#if !FF_MODULE_DISABLE_TOP
|
||||
case 'T':
|
||||
case 't':
|
||||
FF_IF_MODULE_MATCH(ffTopModuleInfo.name) {
|
||||
@@ -120,9 +120,9 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareTopProcesses(options.showTypes);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_WEATHER
|
||||
#if !FF_MODULE_DISABLE_WEATHER
|
||||
case 'W':
|
||||
case 'w':
|
||||
FF_IF_MODULE_MATCH(ffWeatherModuleInfo.name) {
|
||||
@@ -131,7 +131,7 @@ void ffPrepareCommandOption(FFdata* data) {
|
||||
ffPrepareWeather(&options);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef FF_IF_MODULE_MATCH
|
||||
@@ -178,6 +178,34 @@ static void genJsonResult(FFdata* data, FFModuleBaseInfo* baseInfo, void* option
|
||||
}
|
||||
}
|
||||
|
||||
static yyjson_val* findStructureModuleConfig(FFdata* data, const char* moduleType) {
|
||||
if (data->configDoc == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
yyjson_val* root = yyjson_doc_get_root(data->configDoc);
|
||||
if (root == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
yyjson_val* modules = yyjson_obj_get(root, "modules");
|
||||
if (!yyjson_is_arr(modules)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
yyjson_val* item;
|
||||
size_t idx, max;
|
||||
yyjson_arr_foreach (modules, idx, max, item) {
|
||||
if (yyjson_is_obj(item)) {
|
||||
const char* type = yyjson_get_str(yyjson_obj_get(item, "type"));
|
||||
if (type != nullptr && ffStrEqualsIgnCase(type, moduleType)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool parseStructureCommand(
|
||||
FFdata* data,
|
||||
const char* line,
|
||||
@@ -191,7 +219,13 @@ static bool parseStructureCommand(
|
||||
if (data->resultDoc != nullptr) {
|
||||
fn(data, baseInfo, optionBuf);
|
||||
} else {
|
||||
baseInfo->printModule(optionBuf);
|
||||
yyjson_val* configModule = findStructureModuleConfig(data, baseInfo->name);
|
||||
if (configModule != nullptr) {
|
||||
baseInfo->parseJsonObject(optionBuf, configModule);
|
||||
baseInfo->printModule(optionBuf);
|
||||
} else {
|
||||
baseInfo->printModule(optionBuf);
|
||||
}
|
||||
}
|
||||
baseInfo->destroyOptions(optionBuf);
|
||||
return true;
|
||||
|
||||
@@ -47,6 +47,7 @@ void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModu
|
||||
FF_PARSE_FORMAT_STRING_CHECKED(&key, &moduleArgs->key, ((FFformatarg[]) {
|
||||
FF_ARG(moduleIndex, "index"),
|
||||
FF_ARG(moduleArgs->keyIcon, "icon"),
|
||||
FF_ARG(moduleName, "module-name"),
|
||||
}));
|
||||
ffStrbufWriteTo(&key, stdout);
|
||||
}
|
||||
|
||||
+23
-3
@@ -13,6 +13,8 @@ enum {
|
||||
SystemLogicalProcessorAndGroupInformation = 107,
|
||||
SystemBasicPerformanceInformation = 123,
|
||||
SystemSecureBootInformation = 146,
|
||||
SystemBasicProcessInformation = 252,
|
||||
SystemHandleCountInformation = 253,
|
||||
};
|
||||
|
||||
typedef struct _PROCESSOR_POWER_INFORMATION {
|
||||
@@ -206,6 +208,20 @@ typedef struct _SYSTEM_BASIC_PERFORMANCE_INFORMATION {
|
||||
SIZE_T PeakCommitment;
|
||||
} SYSTEM_BASIC_PERFORMANCE_INFORMATION, *PSYSTEM_BASIC_PERFORMANCE_INFORMATION;
|
||||
|
||||
typedef struct _SYSTEM_BASICPROCESS_INFORMATION {
|
||||
ULONG NextEntryOffset;
|
||||
HANDLE UniqueProcessId;
|
||||
HANDLE InheritedFromUniqueProcessId;
|
||||
ULONG64 SequenceNumber;
|
||||
UNICODE_STRING ImageName;
|
||||
} SYSTEM_BASICPROCESS_INFORMATION, *PSYSTEM_BASICPROCESS_INFORMATION;
|
||||
|
||||
typedef struct _SYSTEM_HANDLECOUNT_INFORMATION {
|
||||
ULONG ProcessCount;
|
||||
ULONG ThreadCount;
|
||||
ULONG HandleCount;
|
||||
} SYSTEM_HANDLECOUNT_INFORMATION, *PSYSTEM_HANDLECOUNT_INFORMATION;
|
||||
|
||||
NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval);
|
||||
|
||||
/**
|
||||
@@ -691,15 +707,19 @@ static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime)
|
||||
|
||||
static inline bool ffIsWindows10OrGreater() {
|
||||
#if FF_WIN81_COMPAT
|
||||
return SharedUserData->NtMajorVersion >= 10;
|
||||
return SharedUserData->NtBuildNumber >= 10240;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool ffIsWindows11OrGreater() {
|
||||
return SharedUserData->NtMajorVersion > 10 ||
|
||||
(SharedUserData->NtMajorVersion == 10 && SharedUserData->NtBuildNumber >= 22000);
|
||||
return SharedUserData->NtBuildNumber >= 22000;
|
||||
}
|
||||
|
||||
static inline bool ffIsSystemBasicProcessInfoAvailable() { // Includes HandleCountInformation, which was added together
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation#systembasicprocessinformation
|
||||
return SharedUserData->NtBuildNumber >= 26200; // MSDN says it was added in 26100.4770; ntdoc says 25H2
|
||||
}
|
||||
|
||||
NTSYSAPI NTSTATUS NTAPI NtOpenProcessToken(
|
||||
|
||||
@@ -62,6 +62,10 @@ const char* ffCPUAppleCodeToName(uint32_t code) {
|
||||
return "Apple M4 Pro";
|
||||
case 6041:
|
||||
return "Apple M4 Max";
|
||||
case 8142:
|
||||
return "Apple M5";
|
||||
case 6050:
|
||||
return "Apple M5 Pro / Max";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -712,7 +712,7 @@ static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) {
|
||||
while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), "\nphysical id\t:", strlen("\nphysical id\t:")))) {
|
||||
p += strlen("\nphysical id\t:");
|
||||
char* pend;
|
||||
unsigned long long id = strtoul(p, &pend, 10);
|
||||
unsigned long id = strtoul(p, &pend, 10);
|
||||
if (__builtin_expect(id > 64, false)) { // Do 129-socket boards exist?
|
||||
high |= 1ULL << (id - 64);
|
||||
} else {
|
||||
|
||||
@@ -426,7 +426,7 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf
|
||||
ffStrbufAppendS(deviceDir, "/revision");
|
||||
if (ffReadFileBuffer(deviceDir->chars, buffer)) {
|
||||
char* pend;
|
||||
uint64_t revision = strtoul(buffer->chars, &pend, 16);
|
||||
uint32_t revision = (uint32_t) strtoul(buffer->chars, &pend, 16);
|
||||
if (pend != buffer->chars) {
|
||||
ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ static int walkDevTree(di_node_t node, [[maybe_unused]] di_minor_t minor, FFlist
|
||||
gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
|
||||
gpu->type = FF_GPU_TYPE_UNKNOWN;
|
||||
gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
|
||||
gpu->deviceId = strtoul(di_bus_addr(node), nullptr, 16);
|
||||
gpu->deviceId = (uint64_t) strtoull(di_bus_addr(node), nullptr, 16);
|
||||
gpu->frequency = FF_GPU_FREQUENCY_UNSET;
|
||||
gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
|
||||
|
||||
|
||||
@@ -7,32 +7,65 @@
|
||||
#include <winternl.h>
|
||||
#include <wchar.h>
|
||||
|
||||
static bool fillResult(FFInitSystemResult* result, uint32_t ppid, uint32_t pid, uint16_t len, PCWSTR name) {
|
||||
if (ppid != 4 /* System */ || len <= 4 || _wcsnicmp(name + len - 4, L".exe", 4) != 0) { // smss.exe
|
||||
return false;
|
||||
}
|
||||
result->pid = pid;
|
||||
// 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, name);
|
||||
ffGetFileVersion(exePath, NULL, &result->version);
|
||||
ffStrbufSetWS(&result->exe, exePath);
|
||||
ffStrbufSetNWS(&result->name, len - 4, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
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";
|
||||
if (ffIsSystemBasicProcessInfoAvailable()) {
|
||||
// SYSTEM_BASICPROCESS_INFORMATION entries are much smaller than SYSTEM_PROCESS_INFORMATION ones,
|
||||
// so a modest buffer should be enough to contain all processes
|
||||
SYSTEM_BASICPROCESS_INFORMATION buffer[1024];
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemBasicProcessInformation, buffer, sizeof(buffer), NULL);
|
||||
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
|
||||
goto fallback;
|
||||
}
|
||||
for (auto ptr = buffer; ;ptr = (PSYSTEM_BASICPROCESS_INFORMATION) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
|
||||
if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId,
|
||||
(uint32_t)(uintptr_t) ptr->UniqueProcessId,
|
||||
ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer),
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
fallback:
|
||||
{
|
||||
// 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";
|
||||
}
|
||||
// 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";
|
||||
|
||||
for (auto 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 (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId,
|
||||
(uint32_t)(uintptr_t) ptr->UniqueProcessId, len, 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ typedef struct FFPackagesResult {
|
||||
uint32_t brewCask;
|
||||
uint32_t cards;
|
||||
uint32_t choco;
|
||||
uint32_t crux;
|
||||
uint32_t dpkg;
|
||||
uint32_t emerge;
|
||||
uint32_t eopkg;
|
||||
|
||||
@@ -487,6 +487,9 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts,
|
||||
if (FF_PACKAGES_IS_ENABLED(options, APK)) {
|
||||
packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk");
|
||||
}
|
||||
if (FF_PACKAGES_IS_ENABLED(options, CRUX)) {
|
||||
packageCounts->crux += getNumStrings(baseDir, "/var/lib/pkg/db", "\n\n", "crux");
|
||||
}
|
||||
if (FF_PACKAGES_IS_ENABLED(options, DPKG)) {
|
||||
packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg");
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ static void parsePhysicalDisk(int dfd, const char* devName, FFPhysicalDiskOption
|
||||
ssize_t fileSize = ffReadFileDataRelative(dfd, "size", ARRAY_SIZE(blkSize) - 1, blkSize);
|
||||
if (fileSize > 0) {
|
||||
blkSize[fileSize] = 0;
|
||||
size = (uint64_t) strtoul(blkSize, nullptr, 10) * 512;
|
||||
size = (uint64_t) strtoull(blkSize, nullptr, 10) * 512;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ static void appendDevice(
|
||||
if (size)
|
||||
{
|
||||
char* unit = nullptr;
|
||||
device->size = strtoul(size.UTF8String, &unit, 10);
|
||||
device->size = (uint64_t) strtoull(size.UTF8String, &unit, 10);
|
||||
if (*unit == ' ') ++unit;
|
||||
|
||||
switch (*unit)
|
||||
|
||||
@@ -34,7 +34,7 @@ const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResu
|
||||
if (proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo)) != sizeof(taskInfo)) {
|
||||
continue;
|
||||
}
|
||||
result->threads += taskInfo.pti_threadnum;
|
||||
result->threads += (uint32_t) taskInfo.pti_threadnum;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
#include "processes.h"
|
||||
#include "common/mallocHelper.h"
|
||||
#include "common/windows/nt.h"
|
||||
|
||||
#include <ntstatus.h>
|
||||
#include <winternl.h>
|
||||
|
||||
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
|
||||
if (options->countKprocs && ffIsSystemBasicProcessInfoAvailable()) {
|
||||
// SystemHandleCountInformation reports the total process/thread counts directly,
|
||||
// so we don't need to walk the whole process table.
|
||||
SYSTEM_HANDLECOUNT_INFORMATION info = {}; // Seems that kernel only fills the lower 32 bits of the counts, leave the upper 32 bits untouched.
|
||||
if (NT_SUCCESS(NtQuerySystemInformation(SystemHandleCountInformation, &info, sizeof(info), NULL))) {
|
||||
result->processes = info.ProcessCount;
|
||||
result->threads = info.ThreadCount;
|
||||
return nullptr;
|
||||
}
|
||||
// Otherwise fall back to walking the process table
|
||||
}
|
||||
|
||||
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
|
||||
|
||||
// Multiple attempts in case processes change while
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "sound.h"
|
||||
|
||||
const char* ffDetectSound([[maybe_unused]] FFlist* devices /* List of FFSoundDevice */) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -19,11 +19,11 @@ static const char* detectByProcMeminfo(FFlist* result) {
|
||||
|
||||
char* token = nullptr;
|
||||
if ((token = strstr(buf, "SwapTotal:")) != nullptr) {
|
||||
swapTotal = strtoul(token + strlen("SwapTotal:"), nullptr, 10);
|
||||
swapTotal = (uint64_t) strtoull(token + strlen("SwapTotal:"), nullptr, 10);
|
||||
}
|
||||
|
||||
if ((token = strstr(buf, "SwapFree:")) != nullptr) {
|
||||
swapFree = strtoul(token + strlen("SwapFree:"), nullptr, 10);
|
||||
swapFree = (uint64_t) strtoull(token + strlen("SwapFree:"), nullptr, 10);
|
||||
}
|
||||
|
||||
FFSwapResult* swap = FF_LIST_ADD(FFSwapResult, *result);
|
||||
|
||||
@@ -5,79 +5,125 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
const char* ffDetectWallpaper(FFstrbuf* result)
|
||||
{
|
||||
{
|
||||
// Reliable for user-picked static images.
|
||||
NSURL* url = [NSWorkspace.sharedWorkspace desktopImageURLForScreen:NSScreen.mainScreen];
|
||||
if (url.fileURL && ![url.path isEqualToString:@"/System/Library/CoreServices/DefaultDesktop.heic"] /* dynamic wallpapers */)
|
||||
{
|
||||
ffStrbufSetS(result, url.path.UTF8String);
|
||||
return nullptr;
|
||||
}
|
||||
const char* detectFromPlist(FFstrbuf* result) {
|
||||
// For Sonoma and later (macOS 14.0+)
|
||||
// https://github.com/JohnCoates/Aerial/issues/1332
|
||||
NSError* error;
|
||||
NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars];
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
|
||||
error:&error];
|
||||
|
||||
if (error) {
|
||||
return "Failed to read wallpaper plist file";
|
||||
}
|
||||
|
||||
{
|
||||
// For Sonoma
|
||||
// https://github.com/JohnCoates/Aerial/issues/1332
|
||||
NSError* error;
|
||||
NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars];
|
||||
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
|
||||
error:&error];
|
||||
if (!error)
|
||||
{
|
||||
NSArray* choices = [dict valueForKeyPath:@"SystemDefault.Desktop.Content.Choices"];
|
||||
if (choices.count > 0)
|
||||
{
|
||||
NSDictionary* choice = choices[0];
|
||||
NSArray* files = choice[@"Files"];
|
||||
if (files.count > 0)
|
||||
{
|
||||
NSString* file = files[0][@"relative"];
|
||||
ffStrbufAppendS(result, [NSURL URLWithString:file].path.UTF8String);
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString* provider = choice[@"Provider"];
|
||||
NSString* builtinPrefix = @"com.apple.wallpaper.choice.";
|
||||
if ([provider hasPrefix:builtinPrefix])
|
||||
provider = [provider substringFromIndex:builtinPrefix.length];
|
||||
if ([provider isEqualToString:@"sonoma"])
|
||||
ffStrbufSetStatic(result, "macOS Sonoma");
|
||||
else if ([provider isEqualToString:@"aerials"]) // Most builtin aerial wallpapers are private
|
||||
ffStrbufSetStatic(result, "Built-in aerial photography");
|
||||
else
|
||||
ffStrbufAppendF(result, "Built-in %s wallpaper", provider.UTF8String);
|
||||
NSArray* choices = [dict valueForKeyPath:@"SystemDefault.Desktop.Content.Choices"];
|
||||
if (choices.count > 0) {
|
||||
NSDictionary* choice = choices[0];
|
||||
|
||||
NSArray* files = choice[@"Files"];
|
||||
if (files.count > 0) {
|
||||
NSString* file = files[0][@"relative"];
|
||||
ffStrbufSetS(result, [NSURL URLWithString:file].path.UTF8String);
|
||||
}
|
||||
|
||||
if (result->length == 0) {
|
||||
NSData* configData = choice[@"Configuration"];
|
||||
if (configData && configData.length > 0) {
|
||||
NSError* plistError = nil;
|
||||
NSDictionary* configPlist = [NSPropertyListSerialization propertyListWithData:configData
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:&plistError];
|
||||
|
||||
if (!plistError && [configPlist isKindOfClass:NSDictionary.class]) {
|
||||
NSDictionary* urlDict = configPlist[@"url"];
|
||||
if ([urlDict isKindOfClass:NSDictionary.class]) {
|
||||
NSString* relativeUrlString = urlDict[@"relative"];
|
||||
if ([relativeUrlString isKindOfClass:[NSString class]]) {
|
||||
NSURL* fileUrl = [NSURL URLWithString:relativeUrlString];
|
||||
if (fileUrl.fileURL) {
|
||||
ffStrbufSetS(result, fileUrl.path.UTF8String);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result->length == 0) {
|
||||
NSString* provider = choice[@"Provider"];
|
||||
if ([provider isKindOfClass:NSString.class]) {
|
||||
NSString* builtinPrefix = @"com.apple.wallpaper.choice.";
|
||||
if ([provider hasPrefix:builtinPrefix]) {
|
||||
provider = [provider substringFromIndex:builtinPrefix.length];
|
||||
}
|
||||
|
||||
// macOS internal wallpapers
|
||||
if ([provider isEqualToString:@"aerials"]) { // Most builtin aerial wallpapers are private
|
||||
ffStrbufSetStatic(result, "Built-in aerial photography");
|
||||
} else if ([provider isEqualToString:@"default"]) {
|
||||
ffStrbufSetStatic(result, "macOS Default Wallpaper");
|
||||
} else {
|
||||
ffStrbufSetF(result, "Built-in %s wallpaper", provider.UTF8String);
|
||||
}
|
||||
}
|
||||
if (result->length > 0)
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (result->length == 0) {
|
||||
return "Failed to detect wallpaper from plist";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef FF_HAVE_SQLITE3
|
||||
|
||||
{
|
||||
// For Ventura
|
||||
// https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);
|
||||
ffStrbufAppendS(&path, "Library/Application Support/Dock/desktoppicture.db");
|
||||
if (ffSettingsGetSQLite3String(path.chars,
|
||||
#ifdef FF_HAVE_SQLITE3
|
||||
const char* detectFromSQLite(FFstrbuf* result) {
|
||||
// For Ventura
|
||||
// https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);
|
||||
ffStrbufAppendS(&path, "Library/Application Support/Dock/desktoppicture.db");
|
||||
if (ffSettingsGetSQLite3String(path.chars,
|
||||
"SELECT value\n"
|
||||
"FROM preferences\n"
|
||||
"JOIN data ON preferences.data_id=data.ROWID\n"
|
||||
"JOIN pictures ON preferences.picture_id=pictures.ROWID\n"
|
||||
"JOIN displays ON pictures.display_id=displays.ROWID\n"
|
||||
"JOIN spaces ON pictures.space_id=spaces.ROWID\n"
|
||||
"WHERE display_id=1 AND space_id=1 AND key=1", result)
|
||||
)
|
||||
return nullptr;
|
||||
"WHERE display_id=1 AND space_id=1 AND key=1",
|
||||
result)) {
|
||||
return nullptr;
|
||||
}
|
||||
return "Failed to detect wallpaper from SQLite database";
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* detectFromNSWorkspace(FFstrbuf* result) {
|
||||
// Reliable for user-picked static images.
|
||||
NSScreen* mainScreen = NSScreen.mainScreen;
|
||||
if (!mainScreen) {
|
||||
return "Failed to detect wallpaper from NSWorkspace: No main screen found";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (ffOsascript("tell application \"Finder\" to get POSIX path of (get desktop picture as alias)", result))
|
||||
NSURL* url = [NSWorkspace.sharedWorkspace desktopImageURLForScreen:mainScreen];
|
||||
if (url.fileURL) {
|
||||
ffStrbufSetS(result, url.path.UTF8String);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return "All detection methods failed";
|
||||
return "Failed to detect wallpaper from NSWorkspace";
|
||||
}
|
||||
|
||||
const char* ffDetectWallpaper(FFstrbuf* result) {
|
||||
const char* error;
|
||||
|
||||
if (@available(macOS 14.0, *)) {
|
||||
error = detectFromPlist(result);
|
||||
} else {
|
||||
#ifdef FF_HAVE_SQLITE3
|
||||
error = detectFromSQLite(result);
|
||||
#else
|
||||
error = detectFromNSWorkspace(result);
|
||||
#endif
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -114,70 +114,110 @@ static bool isProcessTrusted(DWORD processId, FFProcessType processType, UNICODE
|
||||
|
||||
#define ffStrEqualNWS(str, compareTo) (_wcsnicmp(str, L##compareTo, sizeof(compareTo) - 1) == 0)
|
||||
|
||||
const char* ffDetectWMPlugin(FFstrbuf* pluginName) {
|
||||
// Returns true if the process was recognized as a WM plugin and pluginName was set.
|
||||
static bool handleProcess(FFstrbuf* pluginName, uint32_t pid, const UNICODE_STRING* imageName) {
|
||||
alignas(UNICODE_STRING) uint8_t buffer[4096];
|
||||
UNICODE_STRING* filePath = (UNICODE_STRING*) buffer;
|
||||
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
|
||||
|
||||
// Multiple attempts in case processes change while
|
||||
// we are in the middle of querying them.
|
||||
ULONG size = 0;
|
||||
for (int attempts = 0;; ++attempts) {
|
||||
if (size) {
|
||||
pstart = (SYSTEM_PROCESS_INFORMATION*) realloc(pstart, size);
|
||||
assert(pstart);
|
||||
}
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);
|
||||
if (NT_SUCCESS(status)) {
|
||||
break;
|
||||
} else if (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) {
|
||||
size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;
|
||||
if (imageName->Length == strlen("FancyWM-GUI.exe") * sizeof(wchar_t) &&
|
||||
ffStrEqualNWS(imageName->Buffer, "FancyWM-GUI.exe") &&
|
||||
isProcessTrusted(pid, FF_PROCESS_TYPE_WINDOWS_STORE | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))) {
|
||||
if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, nullptr, pluginName)) {
|
||||
ffStrbufPrependS(pluginName, "FancyWM ");
|
||||
} else {
|
||||
return "NtQuerySystemInformation(SystemProcessInformation) failed";
|
||||
ffStrbufSetStatic(pluginName, "FancyWM");
|
||||
}
|
||||
return true;
|
||||
} else if (imageName->Length == strlen("glazewm-watcher.exe") * sizeof(wchar_t) &&
|
||||
ffStrEqualNWS(imageName->Buffer, "glazewm-watcher.exe") &&
|
||||
isProcessTrusted(pid, FF_PROCESS_TYPE_SIGNED | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))) {
|
||||
if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, nullptr, pluginName)) {
|
||||
ffStrbufPrependS(pluginName, "GlazeWM ");
|
||||
} else {
|
||||
ffStrbufSetStatic(pluginName, "GlazeWM");
|
||||
}
|
||||
return true;
|
||||
} else if (imageName->Length == strlen("komorebi.exe") * sizeof(wchar_t) &&
|
||||
ffStrEqualNWS(imageName->Buffer, "komorebi.exe") &&
|
||||
isProcessTrusted(pid, FF_PROCESS_TYPE_CUI, filePath, sizeof(buffer))) {
|
||||
if (instance.config.general.detectVersion) {
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateNWS(filePath->Length / sizeof(wchar_t), filePath->Buffer);
|
||||
if (ffProcessAppendStdOut(pluginName, (char* const[]) {
|
||||
path.chars,
|
||||
"--version",
|
||||
nullptr,
|
||||
}) == nullptr) {
|
||||
ffStrbufSubstrBeforeFirstC(pluginName, '\n');
|
||||
}
|
||||
}
|
||||
if (pluginName->length == 0) {
|
||||
ffStrbufSetStatic(pluginName, "Komorebi");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (SYSTEM_PROCESS_INFORMATION* ptr = pstart;; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr->ImageName.Length == 0 || ptr->ImageName.MaximumLength >= ptr->ImageName.Length + 2); // nullptr terminated
|
||||
if (ptr->ImageName.Length == strlen("FancyWM-GUI.exe") * sizeof(wchar_t) &&
|
||||
ffStrEqualNWS(ptr->ImageName.Buffer, "FancyWM-GUI.exe") &&
|
||||
isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_WINDOWS_STORE | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))) {
|
||||
if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, nullptr, pluginName)) {
|
||||
ffStrbufPrependS(pluginName, "FancyWM ");
|
||||
} else {
|
||||
ffStrbufSetStatic(pluginName, "FancyWM");
|
||||
}
|
||||
break;
|
||||
} else if (ptr->ImageName.Length == strlen("glazewm-watcher.exe") * sizeof(wchar_t) &&
|
||||
ffStrEqualNWS(ptr->ImageName.Buffer, "glazewm-watcher.exe") &&
|
||||
isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_SIGNED | FF_PROCESS_TYPE_GUI, filePath, sizeof(buffer))) {
|
||||
if (instance.config.general.detectVersion && ffGetFileVersion(filePath->Buffer, nullptr, pluginName)) {
|
||||
ffStrbufPrependS(pluginName, "GlazeWM ");
|
||||
} else {
|
||||
ffStrbufSetStatic(pluginName, "GlazeWM");
|
||||
}
|
||||
break;
|
||||
} else if (ptr->ImageName.Length == strlen("komorebi.exe") * sizeof(wchar_t) &&
|
||||
ffStrEqualNWS(ptr->ImageName.Buffer, "komorebi.exe") &&
|
||||
isProcessTrusted((DWORD) (uintptr_t) ptr->UniqueProcessId, FF_PROCESS_TYPE_CUI, filePath, sizeof(buffer))) {
|
||||
if (instance.config.general.detectVersion) {
|
||||
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateNWS(filePath->Length / sizeof(wchar_t), filePath->Buffer);
|
||||
if (ffProcessAppendStdOut(pluginName, (char* const[]) {
|
||||
path.chars,
|
||||
"--version",
|
||||
nullptr,
|
||||
}) == nullptr) {
|
||||
ffStrbufSubstrBeforeFirstC(pluginName, '\n');
|
||||
}
|
||||
}
|
||||
if (pluginName->length == 0) {
|
||||
ffStrbufSetStatic(pluginName, "Komorebi");
|
||||
}
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* ffDetectWMPlugin(FFstrbuf* pluginName) {
|
||||
if (ffIsSystemBasicProcessInfoAvailable()) {
|
||||
// Unlike SystemProcessInformation, SystemBasicProcessInformation only returns processes
|
||||
// (no threads), so a single query with a modest buffer is enough.
|
||||
ULONG size = 0;
|
||||
if (NtQuerySystemInformation(SystemBasicProcessInformation, nullptr, 0, &size) != STATUS_INFO_LENGTH_MISMATCH) {
|
||||
goto fallback;
|
||||
}
|
||||
// The process table may change between the two calls; retry with a larger buffer.
|
||||
size += size / 8 + sizeof(SYSTEM_BASICPROCESS_INFORMATION);
|
||||
FF_AUTO_FREE SYSTEM_BASICPROCESS_INFORMATION* pstart = malloc(size);
|
||||
|
||||
if (!NT_SUCCESS(NtQuerySystemInformation(SystemBasicProcessInformation, pstart, size, &size))) {
|
||||
goto fallback;
|
||||
}
|
||||
|
||||
if (ptr->NextEntryOffset == 0) {
|
||||
break;
|
||||
for (auto ptr = pstart;; ptr = (SYSTEM_BASICPROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr >= pstart && (uint8_t*) ptr < (uint8_t*) pstart + size);
|
||||
if (handleProcess(pluginName, (uint32_t)(uintptr_t) ptr->UniqueProcessId, &ptr->ImageName)) {
|
||||
return nullptr;
|
||||
}
|
||||
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
|
||||
if (!ptr->NextEntryOffset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fallback:
|
||||
{
|
||||
FF_AUTO_FREE SYSTEM_PROCESS_INFORMATION* pstart = nullptr;
|
||||
|
||||
// Multiple attempts in case processes change while
|
||||
// we are in the middle of querying them.
|
||||
ULONG size = 0;
|
||||
for (int attempts = 0;; ++attempts) {
|
||||
if (size) {
|
||||
pstart = (SYSTEM_PROCESS_INFORMATION*) realloc(pstart, size);
|
||||
assert(pstart);
|
||||
}
|
||||
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, pstart, size, &size);
|
||||
if (NT_SUCCESS(status)) {
|
||||
break;
|
||||
} else if (status == STATUS_INFO_LENGTH_MISMATCH && attempts < 4) {
|
||||
size += sizeof(SYSTEM_PROCESS_INFORMATION) * 5;
|
||||
} else {
|
||||
return "NtQuerySystemInformation(SystemProcessInformation) failed";
|
||||
}
|
||||
}
|
||||
|
||||
for (auto ptr = pstart;; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
|
||||
assert(ptr->ImageName.Length == 0 || ptr->ImageName.MaximumLength >= ptr->ImageName.Length + 2); // nullptr terminated
|
||||
if (handleProcess(pluginName, (uint32_t)(uintptr_t) ptr->UniqueProcessId, &ptr->ImageName)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (ptr->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,26 @@ static const FFlogo A[] = {
|
||||
.colorTitle = FF_COLOR_FG_CYAN,
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_ALT_ATOMIC
|
||||
// ALT Atomic
|
||||
{
|
||||
.names = { "alt-atomic" },
|
||||
.lines = FASTFETCH_DATATEXT_LOGO_ALT_ATOMIC,
|
||||
.colors = {
|
||||
FF_COLOR_FG_RGB "79;157;207",
|
||||
FF_COLOR_FG_RGB "113;174;213",
|
||||
FF_COLOR_FG_RGB "61;120;158",
|
||||
FF_COLOR_FG_RGB "244;250;252",
|
||||
FF_COLOR_FG_RGB "207;230;243",
|
||||
FF_COLOR_FG_RGB "29;58;76",
|
||||
FF_COLOR_FG_RGB "213;201;181",
|
||||
FF_COLOR_FG_RGB "150;180;193",
|
||||
FF_COLOR_FG_RGB "161;204;230",
|
||||
},
|
||||
.colorKeys = FF_COLOR_FG_RGB "80;157;207",
|
||||
.colorTitle = FF_COLOR_FG_RGB "255;209;164",
|
||||
},
|
||||
#endif
|
||||
#ifdef FASTFETCH_DATATEXT_LOGO_ALTLINUX
|
||||
// ALTLinux
|
||||
{
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
$6.:$3-==$1++++++$3==-$6:.
|
||||
$6.:$3=$1++++++++++++++++++$3=$6:.
|
||||
$6:$3=$1+++++++++++$2*$1++++++++++++$3=$6:
|
||||
$6.$3=$1+++++++++++$9%$4@@@$5@$9#$1+++++++++++$3=$6.
|
||||
$3-$1+++++++++++$2+$4@@$9%$1+$2*$5@$4@$9#$1++++++++++++$3-
|
||||
$3=$1++++++++++++$5%$4@$9%$1++++$5@$4@$2*$1++++++++++++$3=
|
||||
$3-$1+++++$8*******$9#$4@@$2*$1++++$9#$4@$5%$2*****$1++++++++$3-
|
||||
$6:$1+++++$7#$8*$1++++++$5%$4@$5%$9#$5%$4@@@@@@@$5@%%%%$9%$2*$1++++++$6:
|
||||
$3=$1+++++$8*$7%$8*$1+++$2*$9%$4@@@@@$5%$9###$5%$9%$8#*$2*$1+++$2**$1++++++$3=
|
||||
$1++++++++$7#%$5%$4@@$5@@%$9#$1+$2*$7#$8#$1++$5%@$2*+$8*#*$2***$1+++++++
|
||||
$1+++++++$9#$4@@$5%$7%%#$5%@$2*$1++$8#*$1+$2*$5@%$2*$1+$2**$8#$7##$1++++++++
|
||||
$3=$1+++++$5%$4@$2*$1+=+$2+$8*$4@@$7%%#$8**$2*$8*$5@%$2**$1++++$7#%$1++++++$3=
|
||||
$6:$1+++++$9##$2****$1++$5%$4@$5%$2*$8*$7###%%%#######$8*$1++++++$6:
|
||||
$3-$1++++++$2****$1++$9#$4@@$2*$1++++$5%$4@$5%$2*$8***$2**$1+++++++$3-
|
||||
$3=$1++++++++++++$5@$4@$5%$1++++$2*$9#$2*$1++++++++++++$3=
|
||||
$3-$1+++++++++++$9#$4@@$9#$1+++++++++++++++++$3-
|
||||
$6.$3=$1++++++++++$9#$5@$4@$5@$9#$1+++++++++++++$3=$6.
|
||||
$6:$3=$1+++++++++$2*$9###$1+++++++++++$3=$6:
|
||||
$6.:$3=$1++++++++++++++++++$3=$6:.
|
||||
$6.:$3-==$1++++++$3==-$6:.
|
||||
@@ -15,7 +15,8 @@ FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData) {
|
||||
// clang-format off
|
||||
FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR,
|
||||
"libMagickCore-6.Q16HDRI" FF_LIBRARY_EXTENSION, 8,
|
||||
"libMagickCore-6.Q16" FF_LIBRARY_EXTENSION, 8
|
||||
"libMagickCore-6.Q16" FF_LIBRARY_EXTENSION, 8,
|
||||
"libMagickCore-6" FF_LIBRARY_EXTENSION, 8
|
||||
)
|
||||
// clang-format on
|
||||
FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
|
||||
|
||||
@@ -20,7 +20,8 @@ FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData) {
|
||||
#else
|
||||
FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR,
|
||||
"libMagickCore-7.Q16HDRI" FF_LIBRARY_EXTENSION, 11,
|
||||
"libMagickCore-7.Q16" FF_LIBRARY_EXTENSION, 11
|
||||
"libMagickCore-7.Q16" FF_LIBRARY_EXTENSION, 11,
|
||||
"libMagickCore-7" FF_LIBRARY_EXTENSION, 11
|
||||
)
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
@@ -21,6 +21,7 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(result->modelName, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Battery), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ bool ffPrintBios(FFBiosOptions* options) {
|
||||
FF_PARSE_FORMAT_STRING_CHECKED(&key, &options->moduleArgs.key, ((FFformatarg[]) {
|
||||
FF_ARG(bios.type, "type"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Bios), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ static void printDevice(FFBluetoothRadioOptions* options, const FFBluetoothRadio
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(radio->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(BluetoothRadio), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ bool ffPrintBrightness(FFBrightnessOptions* options) {
|
||||
FF_ARG(moduleIndex, "index"),
|
||||
FF_ARG(item->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Brightness), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ static void printBtrfs(FFBtrfsOptions* options, FFBtrfsResult* result, uint8_t i
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(result->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Btrfs), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ static void printCodecLine(const FFCodecOptions* options, uint8_t index, FFstrbu
|
||||
FF_ARG(*gpu, "gpu"),
|
||||
FF_ARG(direction, "direction"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Codec), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ static void printCPUCacheNormal(const FFCPUCacheResult* result, FFCPUCacheOption
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(levelStr, "level"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(CPUCache), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ static void printDisk(FFDiskOptions* options, const FFDisk* disk, uint32_t index
|
||||
FF_ARG(disk->filesystem, "filesystem"),
|
||||
FF_ARG(mountpointLink, "mountpoint-link"),
|
||||
FF_ARG(nameLink, "name-link"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Disk), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ static void formatKey(const FFDiskIOOptions* options, FFDiskIOResult* dev, uint3
|
||||
FF_ARG(dev->name, "name"),
|
||||
FF_ARG(dev->devPath, "dev-path"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(DiskIO), "module-name"),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ bool ffPrintDisplay(FFDisplayOptions* options) {
|
||||
FF_ARG(result->name, "name"),
|
||||
FF_ARG(displayType, "type"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Display), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ bool ffPrintLoadavg(FFLoadavgOptions* options) {
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(duration, "duration"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Loadavg), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ static void formatKey(const FFLocalIpOptions* options, FFLocalIpResult* ip, uint
|
||||
FF_ARG(ip->name, "ifname"),
|
||||
FF_ARG(ip->mac, "mac"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(LocalIP), "module-name"),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ bool ffPrintMonitor(FFMonitorOptions* options) {
|
||||
FF_ARG(moduleIndex, "index"),
|
||||
FF_ARG(display->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Monitor), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ static void formatKey(const FFNetIOOptions* options, FFNetIOResult* inf, uint32_
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(inf->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(NetIO), "module-name"),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ bool ffPrintOS(FFOSOptions* options) {
|
||||
FF_ARG(instance.state.platform.sysinfo.name, "sysname"),
|
||||
FF_ARG(os->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(OS), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ typedef enum FFPackagesFlags: uint64_t {
|
||||
FF_PACKAGES_FLAG_CARDS_BIT = UINT64_C(1) << 34U,
|
||||
FF_PACKAGES_FLAG_PORG_BIT = UINT64_C(1) << 35U,
|
||||
FF_PACKAGES_FLAG_INSTALLRELEASE_BIT = UINT64_C(1) << 36U,
|
||||
FF_PACKAGES_FLAG_CRUX_BIT = UINT64_C(1) << 37U,
|
||||
} FFPackagesFlags;
|
||||
static_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), "");
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ bool ffPrintPackages(FFPackagesOptions* options) {
|
||||
}
|
||||
FF_PRINT_PACKAGE(cards)
|
||||
FF_PRINT_PACKAGE(choco)
|
||||
FF_PRINT_PACKAGE(crux)
|
||||
FF_PRINT_PACKAGE(dpkg)
|
||||
FF_PRINT_PACKAGE(emerge)
|
||||
FF_PRINT_PACKAGE(eopkg)
|
||||
@@ -154,6 +155,7 @@ bool ffPrintPackages(FFPackagesOptions* options) {
|
||||
FF_ARG(counts.brewCask, "brew-cask"),
|
||||
FF_ARG(counts.cards, "cards"),
|
||||
FF_ARG(counts.choco, "choco"),
|
||||
FF_ARG(counts.choco, "crux"),
|
||||
FF_ARG(counts.dpkg, "dpkg"),
|
||||
FF_ARG(counts.emerge, "emerge"),
|
||||
FF_ARG(counts.eopkg, "eopkg"),
|
||||
@@ -257,6 +259,7 @@ void ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module) {
|
||||
;
|
||||
FF_TEST_PACKAGE_NAME(CARDS)
|
||||
FF_TEST_PACKAGE_NAME(CHOCO)
|
||||
FF_TEST_PACKAGE_NAME(CRUX)
|
||||
break;
|
||||
case 'D':
|
||||
if (false)
|
||||
@@ -390,6 +393,7 @@ void ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* do
|
||||
FF_TEST_PACKAGE_NAME(BREW)
|
||||
FF_TEST_PACKAGE_NAME(CARDS)
|
||||
FF_TEST_PACKAGE_NAME(CHOCO)
|
||||
FF_TEST_PACKAGE_NAME(CRUX)
|
||||
FF_TEST_PACKAGE_NAME(DPKG)
|
||||
FF_TEST_PACKAGE_NAME(EMERGE)
|
||||
FF_TEST_PACKAGE_NAME(EOPKG)
|
||||
@@ -451,6 +455,7 @@ bool ffGeneratePackagesJsonResult(FFPackagesOptions* options, yyjson_mut_doc* do
|
||||
FF_APPEND_PACKAGE_COUNT(brewCask)
|
||||
FF_APPEND_PACKAGE_COUNT(cards)
|
||||
FF_APPEND_PACKAGE_COUNT(choco)
|
||||
FF_APPEND_PACKAGE_COUNT(crux)
|
||||
FF_APPEND_PACKAGE_COUNT(dpkg)
|
||||
FF_APPEND_PACKAGE_COUNT(emerge)
|
||||
FF_APPEND_PACKAGE_COUNT(eopkg)
|
||||
@@ -549,6 +554,7 @@ FFModuleBaseInfo ffPackagesModuleInfo = {
|
||||
{ "Number of brew-cask packages", "brew-cask" },
|
||||
{ "Number of cards packages", "cards" },
|
||||
{ "Number of choco packages", "choco" },
|
||||
{ "Number of crux packages", "crux" },
|
||||
{ "Number of dpkg packages", "dpkg" },
|
||||
{ "Number of emerge packages", "emerge" },
|
||||
{ "Number of eopkg packages", "eopkg" },
|
||||
|
||||
@@ -19,6 +19,7 @@ static void formatKey(const FFPhysicalDiskOptions* options, FFPhysicalDiskResult
|
||||
FF_ARG(dev->name, "name"),
|
||||
FF_ARG(dev->devPath, "dev-path"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(PhysicalDisk), "module-name"),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ void printSwap(FFSwapOptions* options, uint8_t index, uint32_t totalCount, FFSwa
|
||||
FF_ARG(index, "index"),
|
||||
FF_ARG(storage->name, "name"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Swap), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t i
|
||||
FF_ARG(result->name, "name"),
|
||||
FF_ARG(result->guid, "guid"),
|
||||
FF_ARG(options->moduleArgs.keyIcon, "icon"),
|
||||
FF_ARG(FF_MODULE_GET_DISPLAY_NAME(Zpool), "module-name"),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user