mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 07:44:06 +01:00
Global: fixes a lot bugs found by AI
This commit is contained in:
@@ -6,8 +6,10 @@ Changes:
|
||||
* ImageMagick 6 support is deprecated. It is kept only for old Debian and Ubuntu releases that don't have ImageMagick 7 available, and is intended to be removed in a future release. Users are encouraged to upgrade to ImageMagick 7 when possible.
|
||||
* The `--logo-recache` option has been replaced by `--logo-cache <bool|regen>`, and the `logo.recache` JSON property has been renamed to `logo.cache`. (Logo)
|
||||
* `--logo-cache true` (the default) reuses a cached rendering when it is valid, and writes it back on a cache miss; `false` ignores the image logo cache completely, reading nothing from it and writing nothing to it; `regen` does what `--logo-recache true` used to do. `logo.cache` accepts a boolean, or the string `"regen"`.
|
||||
* Load average detection on GNU/Hurd now uses `getloadavg()` instead of reading `/proc/loadavg`, sharing the Solaris implementation. (Loadavg, GNU/Hurd)
|
||||
|
||||
Features:
|
||||
* Added Bluetooth Core 6.0, 6.1, 6.2 and 6.3 to the `BluetoothRadio` version table, so an adapter reporting LMP version 14, 15, 16 or 17 now prints its version instead of the vendor name alone. (BluetoothRadio)
|
||||
* Added rum overlay package counting on RakuOS, exposed as `{rum}`. (Packages, Linux)
|
||||
* Improved image logo support
|
||||
* Backend rewritten
|
||||
@@ -91,6 +93,65 @@ Bugfixes:
|
||||
* Increased the terminal response wait time to 1s to improve compatibility with slower terminals. (General)
|
||||
* Fixed the `PublicIp` and `Weather` modules exiting with "can only be used once due to internal limitations" on the round after a failed request when `--dynamic-interval` is used, instead of retrying. (PublicIp / Weather)
|
||||
* Fixed the `Display`, `Monitor`, `WM`, `DE`, `Media`, `Player`, `Shell` and `Terminal` modules replaying the first round's result when `--dynamic-interval` is used. The detection results that are cached for the whole run are now dropped between rounds, so these modules detect again on every round. (Display / Monitor / WM / DE / Media / Player / Shell / Terminal)
|
||||
* Fixed the `Command` module printing the internal marker `[BUG] command queue is empty` when it is selected with `--structure`; it now reports that module options are unsupported and exits with an error. (Command)
|
||||
* Fixed `condition.succeeded` being evaluated differently in the prepare and the print pass, which could make a module print another module's output. (General)
|
||||
* Fixed negative values for numeric options wrapping around to huge unsigned ones, which made `separator.times` write four billion characters and `waitTime` sleep for about 50 days. (Separator / CpuUsage / NetIO / DiskIO / LoadAvg)
|
||||
* Fixed the module index wrapping around at 256 in the `command.splitLines` output. (Command)
|
||||
* Fixed carriage returns not being stripped from the `command.splitLines` output on Windows. (Command, Windows)
|
||||
* Fixed the `logo` module's JSON `type` never containing `"normal"`. (Logo)
|
||||
* Fixed the JSON schema describing `custom.key` as the opposite of what the module does. (Custom)
|
||||
* Fixed the JSON schema rejecting the `custom` and `logo` module types, the `GNU` and `Unknown` systems and the `sh` and `Unknown` architectures. (General)
|
||||
* Fixed the JSON schema giving `command` a description different from the module registry. (Command)
|
||||
* Added a JSON result to the `custom` module. (Custom)
|
||||
* Fixed the `gpu` and `top` modules both declaring default order 36. (General)
|
||||
* Removed the `kernel` format variable `{display-version}`, which was declared but never passed. (Kernel)
|
||||
* Fixed `editor` detection treating a failure as a success and printing an empty or half-filled line without an error. (Editor)
|
||||
* Fixed the `display` module's `order` option being ignored by `--format json`. (Display)
|
||||
* Fixed compact mode printing `120Hz` where the normal mode prints `120 Hz`. (Display)
|
||||
* Fixed the `display` module sorting the process-wide display server result in place, which reordered it for every other consumer. (Display)
|
||||
* Removed 14 `*_nosupport.c` files that no platform block referenced. (General)
|
||||
* Fixed the `media` module leaking a downloaded cover file on every run. (Media)
|
||||
* Fixed the JSON schema for `packages.disabled` rejecting 8 names the parser accepts, and dropping the build-time default. (Packages)
|
||||
* Fixed the JSON schema declaring `percent` for the `cpucache`, `cursor`, `datetime`, `editor` and `font` modules, which the parser rejects, and added the missing declaration for `wifi`. (General)
|
||||
* Fixed the `custom` module's JSON `error` field being a dangling pointer when a script failed. (Custom)
|
||||
* Fixed the `vulkan` module writing `deviceId` twice into every GPU object of its JSON result. (Vulkan)
|
||||
* Fixed the `opencl` and `vulkan` modules writing `memory.*.used` from the total. (OpenCL / Vulkan)
|
||||
* Fixed the `gpu` module's `hideType` filter being applied to the console output but not to `--format json`. (GPU)
|
||||
* Fixed the `gpu` module's OpenGL fallback reporting all four memory fields as `0` instead of "unset", which made a format string print `0 B` and the percentage print `nan%`. (GPU)
|
||||
* Fixed the `de` module not reporting a version for GNOME Classic. (DE, Linux)
|
||||
* Fixed the `de` module's Trinity version detection never matching, as the display server reports `Trinity` or `TDE` rather than the lowercase `trinity` the dispatch compared against. (DE, Linux)
|
||||
* Fixed the JSON schema pointing `keyColor` and `keyWidth` at non-existent `display.color.key` and `display.keyWidth` paths. (General)
|
||||
* Fixed the JSON schema rejecting an empty module `key`, which the parser accepts and treats as "use the default name". (General)
|
||||
* Fixed the JSON schema for `separator.times` having no upper bound, so values the runtime rejects passed validation. (Separator)
|
||||
* Fixed the JSON schema declaring `showPeCoreCount` as defaulting to `false` when the runtime defaults it to `true`. (CPU)
|
||||
* Fixed the JSON schema for `cpucache` not declaring the `compact` option, which made a valid configuration fail validation. (CPUCache)
|
||||
* Fixed the JSON schema for `cpu` omitting the x86-only `{code-name}` and `{technology}` format variables. (CPU)
|
||||
* Added the missing `{threads}` format variable to the `processes` module, which its default output already printed. (Processes)
|
||||
* Added the missing `{kernel-release}` format variable to the `os` module. (OS)
|
||||
* Fixed the `poweradapter` module documenting `{model}` while passing the value as `{model-name}`, so the documented variable was never substituted. (PowerAdapter)
|
||||
* Fixed the JSON schema for `processes` omitting `{threads}`. (Processes)
|
||||
* Fixed the `datetime` module's `{day-in-year}` variable wrapping around at day 256 and printing a wrong value for the rest of the year. (DateTime)
|
||||
* Fixed the `datetime` module's `{hour-12}` variable being `0`-`11` while `{hour-12-pretty}` is `01`-`12`, so the two disagreed at midnight and noon. (DateTime)
|
||||
* Fixed the `colors` module using out-of-range `paddingLeft` and `block.width` values instead of reporting them: `paddingLeft: -1` made the run write four billion spaces without ever finishing, and `block.width` was truncated to a byte (`256` printed nothing, `300` printed 44 cells). (Colors)
|
||||
* Fixed the `vulkan` module's JSON result missing `instanceVersion`, which the detection layer already provides. (Vulkan)
|
||||
* Fixed the `vulkan` module reporting every device that is not discrete as `Integrated`, so virtual and `OTHER` devices are no longer indistinguishable from a real integrated GPU. (Vulkan)
|
||||
* Fixed the `camera` and `wifi` modules printing "Not support on this platform" instead of "Not supported on this platform" on platforms that have no implementation. (Camera / Wifi)
|
||||
* Fixed the `colors` module reading a `block.range` element that is not an integer as `0`, so `[1.5, 3]` and `["x", 3]` printed the colours of `[0, 3]` without reporting anything. (Colors)
|
||||
* Fixed the `bluetooth` module numbering a machine's only adapter (`Bluetooth 1`) instead of using the bare module name, which every other multi-entry module gets right. (Bluetooth)
|
||||
* Fixed the `camera` module leaving `colorspace` uninitialized in its macOS backend, so a device reporting a colour space outside the known list produced a garbage string. (Camera, macOS)
|
||||
* Added the missing `battery` field to the `gamepad` module's JSON result, which the detection layer already provides and the console output already prints. (Gamepad)
|
||||
* Fixed the `gamepad` module leaking the name and the serial of every device when all of them are filtered out by `ignores`. (Gamepad)
|
||||
* Fixed the `dns` module appending an uninitialized entry on Windows when an adapter reports a DNS server whose address family is neither IPv4 nor IPv6. (DNS, Windows)
|
||||
* Added the missing `globalReservation` object (`used` / `total`) to the `btrfs` module's JSON result. The detection layer had been reading `allocation/global_rsv_size` and `allocation/global_rsv_reserved` into fields that neither the JSON nor a format variable exposed. (Btrfs)
|
||||
* Fixed the `btrfs` module storing the free part of the global reservation as its used part. (Btrfs)
|
||||
* Fixed the `keyboard` module leaking the name and the serial of every device that is filtered out by `ignores`. (Keyboard)
|
||||
* Fixed the `keyboard` module reporting "No mouse support on this platform" instead of "No keyboard support on this platform" on platforms that have no implementation. (Keyboard)
|
||||
* Fixed the `localip` module's `{is-default-route}` variable printing the internal bit field (`2`, `4`, `6`) instead of the families the interface carries the default route for (`ipv4`, `ipv6`, `ipv4,ipv6`). (LocalIp)
|
||||
* Fixed the `sound` module always reporting itself as failed, which suppressed every following module gated on `condition.succeeded`. (Sound)
|
||||
* Fixed the `top` module rejecting `sort: "threads"`, which the JSON schema documents and `--gen-config` emits. (Top)
|
||||
* Fixed the `display.temp` validation errors naming the long-removed `display.temperature` key, which the config parser rejects. (General)
|
||||
* Fixed a memory leak in the `wifi` module on Windows when the BSS list comes back empty. (Wifi, Windows)
|
||||
* Fixed `--gen-config` hanging forever when stdout is redirected to `NUL` or MSYS2's `/dev/null`. On Windows `isatty()` reports a character device rather than a console, so a redirected run was mistaken for an interactive one. (General, Windows)
|
||||
* Some internal cleanups and optimizations.
|
||||
|
||||
Logos:
|
||||
|
||||
+1
-1
@@ -1343,7 +1343,7 @@ elseif(GNU)
|
||||
src/detection/keyboard/keyboard_nosupport.c
|
||||
src/detection/libc/libc_linux.c
|
||||
src/detection/lm/lm_linux.c
|
||||
src/detection/loadavg/loadavg_linux.c
|
||||
src/detection/loadavg/loadavg_sunos.c
|
||||
src/detection/locale/locale_linux.c
|
||||
src/detection/localip/localip_linux.c
|
||||
src/detection/gamepad/gamepad_nosupport.c
|
||||
|
||||
+38
-31
@@ -19,15 +19,14 @@
|
||||
},
|
||||
"key": {
|
||||
"description": "Module key\nUse a single space (` `) to hide the key",
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
"type": "string"
|
||||
},
|
||||
"keyColor": {
|
||||
"description": "Color of the module key. Overrides `display.color.key`",
|
||||
"description": "Color of the module key. Overrides `display.color.keys`",
|
||||
"$ref": "#/$defs/colors"
|
||||
},
|
||||
"keyWidth": {
|
||||
"description": "Width of the module key. Overrides `display.keyWidth`",
|
||||
"description": "Width of the module key. Overrides `display.key.width`",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
@@ -131,7 +130,9 @@
|
||||
"SunOS",
|
||||
"OpenBSD",
|
||||
"NetBSD",
|
||||
"Haiku"
|
||||
"Haiku",
|
||||
"GNU",
|
||||
"Unknown"
|
||||
]
|
||||
},
|
||||
"architectures": {
|
||||
@@ -150,7 +151,9 @@
|
||||
"sparc",
|
||||
"alpha",
|
||||
"hppa",
|
||||
"m68k"
|
||||
"sh",
|
||||
"m68k",
|
||||
"Unknown"
|
||||
]
|
||||
},
|
||||
"conditions": {
|
||||
@@ -322,7 +325,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"cpuFormat": {
|
||||
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count",
|
||||
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count\n 13. {code-name}: CPU code name (x86 only)\n 14. {technology}: CPU technology (x86 only)",
|
||||
"type": "string"
|
||||
},
|
||||
"cpucacheFormat": {
|
||||
@@ -390,7 +393,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"kernelFormat": {
|
||||
"description": "Output format for the `Kernel` module. See Wiki for formatting syntax\n 1. {sysname}: Sysname\n 2. {release}: Release\n 3. {version}: Version\n 4. {arch}: Architecture\n 5. {display-version}: Display version\n 6. {page-size}: Page size",
|
||||
"description": "Output format for the `Kernel` module. See Wiki for formatting syntax\n 1. {sysname}: Sysname\n 2. {release}: Release\n 3. {version}: Version\n 4. {arch}: Architecture\n 5. {page-size}: Page size",
|
||||
"type": "string"
|
||||
},
|
||||
"keyboardFormat": {
|
||||
@@ -442,7 +445,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"osFormat": {
|
||||
"description": "Output format for the `OS` module. See Wiki for formatting syntax\n 1. {sysname}: Name of the kernel *\n 2. {name}: Name of the OS *\n 3. {pretty-name}: Pretty name of the OS, if available\n 4. {id}: ID of the OS\n 5. {id-like}: ID like of the OS\n 6. {variant}: Variant of the OS\n 7. {variant-id}: Variant ID of the OS\n 8. {version}: Version of the OS\n 9. {version-id}: Version ID of the OS\n 10. {codename}: Version codename of the OS\n 11. {build-id}: Build ID of the OS\n 12. {arch}: Architecture of the OS",
|
||||
"description": "Output format for the `OS` module. See Wiki for formatting syntax\n 1. {sysname}: Name of the kernel *\n 2. {name}: Name of the OS *\n 3. {pretty-name}: Pretty name of the OS, if available\n 4. {id}: ID of the OS\n 5. {id-like}: ID like of the OS\n 6. {variant}: Variant of the OS\n 7. {variant-id}: Variant ID of the OS\n 8. {version}: Version of the OS\n 9. {version-id}: Version ID of the OS\n 10. {codename}: Version codename of the OS\n 11. {build-id}: Build ID of the OS\n 12. {arch}: Architecture of the OS\n 13. {kernel-release}: Release of the kernel",
|
||||
"type": "string"
|
||||
},
|
||||
"packagesFormat": {
|
||||
@@ -462,11 +465,11 @@
|
||||
"type": "string"
|
||||
},
|
||||
"poweradapterFormat": {
|
||||
"description": "Output format for the `PowerAdapter` module. See Wiki for formatting syntax\n 1. {watts}: Power adapter watts\n 2. {name}: Power adapter name\n 3. {manufacturer}: Power adapter manufacturer\n 4. {model}: Power adapter model\n 5. {description}: Power adapter description\n 6. {serial}: Power adapter serial number",
|
||||
"description": "Output format for the `PowerAdapter` module. See Wiki for formatting syntax\n 1. {watts}: Power adapter watts\n 2. {name}: Power adapter name\n 3. {manufacturer}: Power adapter manufacturer\n 4. {model-name}: Power adapter model\n 5. {description}: Power adapter description\n 6. {serial}: Power adapter serial number",
|
||||
"type": "string"
|
||||
},
|
||||
"processesFormat": {
|
||||
"description": "Output format for the `Processes` module. See Wiki for formatting syntax\n 1. {result}: Process count",
|
||||
"description": "Output format for the `Processes` module. See Wiki for formatting syntax\n 1. {result}: Process count\n 2. {threads}: Thread count",
|
||||
"type": "string"
|
||||
},
|
||||
"publicipFormat": {
|
||||
@@ -1576,6 +1579,7 @@
|
||||
"command",
|
||||
"colors",
|
||||
"cursor",
|
||||
"custom",
|
||||
"datetime",
|
||||
"display",
|
||||
"disk",
|
||||
@@ -1595,6 +1599,7 @@
|
||||
"loadavg",
|
||||
"locale",
|
||||
"localip",
|
||||
"logo",
|
||||
"media",
|
||||
"memory",
|
||||
"monitor",
|
||||
@@ -2100,7 +2105,7 @@
|
||||
"showPeCoreCount": {
|
||||
"description": "Whether to detect and display CPU frequencies for different core types (for example, P-cores and E-cores), if supported",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"default": true
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
@@ -2134,8 +2139,10 @@
|
||||
"const": "cpucache",
|
||||
"description": "Print CPU cache sizes"
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
"compact": {
|
||||
"type": "boolean",
|
||||
"description": "Print one line with a total per cache level instead of one line per level",
|
||||
"default": false
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
@@ -2254,6 +2261,7 @@
|
||||
"description": "Set the number of white spaces to print before the symbol",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295,
|
||||
"default": 0
|
||||
},
|
||||
"block": {
|
||||
@@ -2265,6 +2273,7 @@
|
||||
"description": "Set the block width in spaces",
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 9,
|
||||
"default": 3
|
||||
},
|
||||
"range": {
|
||||
@@ -2322,7 +2331,7 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "Run a custom shell command",
|
||||
"description": "Run custom shell scripts",
|
||||
"const": "command"
|
||||
},
|
||||
"shell": {
|
||||
@@ -2384,9 +2393,6 @@
|
||||
"const": "cursor",
|
||||
"description": "Print cursor style name"
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
@@ -2420,8 +2426,7 @@
|
||||
"const": "custom"
|
||||
},
|
||||
"key": {
|
||||
"description": "Leave empty to hide the key",
|
||||
"type": "string"
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
"keyColor": {
|
||||
"$ref": "#/$defs/keyColor"
|
||||
@@ -2453,9 +2458,6 @@
|
||||
"const": "datetime",
|
||||
"description": "Print the current date and time"
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
@@ -2832,9 +2834,6 @@
|
||||
"const": "editor",
|
||||
"description": "Print information about the default editor (`$VISUAL` or `$EDITOR`)"
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
@@ -2867,9 +2866,6 @@
|
||||
"const": "font",
|
||||
"description": "Print system font names"
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
@@ -3775,7 +3771,9 @@
|
||||
"enum": [
|
||||
"am",
|
||||
"apk",
|
||||
"appimage",
|
||||
"brew",
|
||||
"cards",
|
||||
"choco",
|
||||
"crux",
|
||||
"dpkg",
|
||||
@@ -3784,10 +3782,13 @@
|
||||
"flatpak",
|
||||
"guix",
|
||||
"hpkg",
|
||||
"installrelease",
|
||||
"kiss",
|
||||
"linglong",
|
||||
"lpkg",
|
||||
"lpkgbuild",
|
||||
"macports",
|
||||
"moss",
|
||||
"mport",
|
||||
"nix",
|
||||
"opkg",
|
||||
@@ -3796,11 +3797,14 @@
|
||||
"paludis",
|
||||
"pisi",
|
||||
"pkg",
|
||||
"pkgsrc",
|
||||
"pkgtool",
|
||||
"porg",
|
||||
"rpm",
|
||||
"rum",
|
||||
"scoop",
|
||||
"snap",
|
||||
"soar",
|
||||
"sorcery",
|
||||
"winget",
|
||||
"xbps"
|
||||
@@ -3812,8 +3816,7 @@
|
||||
"description": "Enable all package managers",
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": []
|
||||
]
|
||||
},
|
||||
"combined": {
|
||||
"description": "Whether to combine related package managers into single counts (e.g., nix-system + nix-user = nix)",
|
||||
@@ -4101,6 +4104,7 @@
|
||||
"description": "Number of times to repeat the separator string, or 0 to auto-detect",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295,
|
||||
"default": 0
|
||||
},
|
||||
"condition": {
|
||||
@@ -4805,6 +4809,9 @@
|
||||
"const": "wifi",
|
||||
"description": "Print connected Wi-Fi info (SSID, connection and security protocol)"
|
||||
},
|
||||
"percent": {
|
||||
"$ref": "#/$defs/percent"
|
||||
},
|
||||
"key": {
|
||||
"$ref": "#/$defs/key"
|
||||
},
|
||||
|
||||
@@ -85,11 +85,29 @@ 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 || !FF_MODULE_DISABLE_COMMAND
|
||||
case 'C':
|
||||
case 'c':
|
||||
#endif
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE
|
||||
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
|
||||
ffPrepareCPUUsage(); // The rate is derived from the CPU's own counters; no options are involved
|
||||
#endif
|
||||
|
||||
#if !FF_MODULE_DISABLE_COMMAND
|
||||
FF_IF_MODULE_MATCH(ffCommandModuleInfo.name) {
|
||||
// `command` runs its shell commands in parallel: `ffPrepareCommand` spawns them here,
|
||||
// in the prepare pass, and `ffDetectCommand` collects them in the print pass. The
|
||||
// command line itself can only be read from the JSON config, and this code path does
|
||||
// not consult it, so nothing would ever be queued and the module would report an
|
||||
// internal error marker. Refuse it up front instead of letting it get that far.
|
||||
fputs("Error: module `command` is not supported with `--structure`\n", stderr);
|
||||
fputs(" Its options can only be read from the JSON config, which `--structure` bypasses.\n", stderr);
|
||||
fputs(" Add it to the config instead, e.g. `{ \"modules\": [ { \"type\": \"command\", \"text\": \"uname -r\" } ] }`\n", stderr);
|
||||
exit(481);
|
||||
}
|
||||
#endif
|
||||
#if !FF_MODULE_DISABLE_CPUUSAGE || !FF_MODULE_DISABLE_COMMAND
|
||||
break;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -329,6 +329,10 @@ FFNativeFD ffGetNullFD(void) {
|
||||
return hNullFile;
|
||||
}
|
||||
|
||||
bool ffIsTerminal(int fd) {
|
||||
return isatty(fd) != 0;
|
||||
}
|
||||
|
||||
bool ffRemoveFile(const char* fileName) {
|
||||
return unlink(fileName) == 0;
|
||||
}
|
||||
|
||||
@@ -500,6 +500,17 @@ FFNativeFD ffGetNullFD(void) {
|
||||
return hNullFile;
|
||||
}
|
||||
|
||||
bool ffIsTerminal(int fd) {
|
||||
intptr_t handle = _get_osfhandle(fd);
|
||||
if (handle == -1 || handle == -2) {
|
||||
// -1: an invalid fd; -2: a valid fd with no file behind it (`_get_osfhandle`'s way of
|
||||
// saying FF_INVALID_FD). Neither is a terminal.
|
||||
return false;
|
||||
}
|
||||
DWORD mode;
|
||||
return GetConsoleMode((HANDLE) handle, &mode) != 0;
|
||||
}
|
||||
|
||||
bool ffRemoveFile(const char* fileName) {
|
||||
wchar_t fileNameW[MAX_PATH];
|
||||
ULONG len;
|
||||
|
||||
@@ -60,6 +60,22 @@ void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* m
|
||||
}
|
||||
}
|
||||
|
||||
bool ffJsonConfigParseUInt32(yyjson_val* val, uint32_t* result, uint32_t max) {
|
||||
if (!yyjson_is_int(val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `yyjson_get_sint()` is exact for every value that fits into `int64_t`, and is negative for the
|
||||
// `uint64_t` values above `INT64_MAX`, which no caller accepts anyway.
|
||||
int64_t num = unsafe_yyjson_get_sint(val);
|
||||
if (num < 0 || (uint64_t) num > max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = (uint32_t) num;
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]) {
|
||||
if (yyjson_is_int(val)) {
|
||||
int intVal = yyjson_get_int(val);
|
||||
@@ -311,6 +327,16 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
|
||||
if (!unsafe_yyjson_is_bool(previousSucceeded)) {
|
||||
return "Property 'succeeded' in 'condition' must be a boolean";
|
||||
}
|
||||
if (prepare) {
|
||||
// Whether this module is printed at all depends on the result of the previous
|
||||
// one, which is only known in the print pass. Preparing it here would leave a
|
||||
// result behind that no print pass ever consumes, and since the modules are
|
||||
// paired by position (see `ffPrepareCommand` / `ffDetectCommand`), that
|
||||
// would silently shift every later module onto the wrong result.
|
||||
// Skip it, and let the module report the missing preparation if it turns out
|
||||
// to be printed after all.
|
||||
continue;
|
||||
}
|
||||
if (succeeded != unsafe_yyjson_get_bool(previousSucceeded)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "common/textModifier.h"
|
||||
#include "logo/logo.h"
|
||||
|
||||
void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType) {
|
||||
void ffPrintLogoAndKey(const char* moduleName, uint32_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType) {
|
||||
ffLogoPrintLine();
|
||||
|
||||
// This is used by --set-keyless, in this case we want neither the module name nor the separator
|
||||
@@ -40,7 +40,7 @@ void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModu
|
||||
fputs(moduleName, stdout);
|
||||
|
||||
if (moduleIndex > 0) {
|
||||
printf(" %hhu", moduleIndex);
|
||||
printf(" %u", moduleIndex);
|
||||
}
|
||||
} else {
|
||||
FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
|
||||
@@ -82,7 +82,7 @@ void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModu
|
||||
}
|
||||
}
|
||||
|
||||
bool ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments) {
|
||||
bool ffPrintFormat(const char* moduleName, uint32_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments) {
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
bool success;
|
||||
if (__builtin_expect(moduleArgs != nullptr, 1)) {
|
||||
@@ -102,7 +102,7 @@ bool ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleAr
|
||||
return success;
|
||||
}
|
||||
|
||||
void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...) {
|
||||
void ffPrintError(const char* moduleName, uint32_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...) {
|
||||
if (!instance.config.display.showErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -286,6 +286,13 @@ static inline void wrapClosedir(HANDLE* pdir) {
|
||||
}
|
||||
|
||||
FFNativeFD ffGetNullFD(void);
|
||||
// Whether a descriptor is attached to a real terminal, i.e. whether a human is looking at it.
|
||||
//
|
||||
// Do not use `isatty()` for this on Windows. It is `_isatty()`, which answers "is this a character
|
||||
// device" rather than "is this a console", and `NUL` -- as well as MSYS2's `/dev/null` -- is a
|
||||
// character device. A redirected run therefore looks interactive, which is how
|
||||
// `fastfetch --gen-config <path> > /dev/null` ends up on the interactive path and blocks forever.
|
||||
bool ffIsTerminal(int fd);
|
||||
// Returns whether the file was removed; callers that only want it gone discard that, so not `nodiscard`
|
||||
[[gnu::nonnull(1)]] bool ffRemoveFile(const char* fileName);
|
||||
// Modification time of a file, in milliseconds since the Unix epoch, or 0 if it can not be read.
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
// `val` may be null (yyjson predicates tolerate it); `result` and `pairs` may not.
|
||||
// Returns an error string, null on success, so it is `nodiscard`.
|
||||
[[gnu::nonnull(2, 3), nodiscard]] const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]);
|
||||
// Reads an integer in `[0, max]` into `result`. Returns false for anything else.
|
||||
//
|
||||
// `yyjson_get_uint()` accepts the signed number subtype as well and reinterprets its bits, so the
|
||||
// bare `(uint32_t) yyjson_get_uint(val)` it replaces turned `-1` into `UINT32_MAX` — 4.3 billion
|
||||
// characters for `separator.times`, a ~50 day sleep for `waitTime`.
|
||||
[[gnu::nonnull(1, 2), nodiscard]] bool ffJsonConfigParseUInt32(yyjson_val* val, uint32_t* result, uint32_t max);
|
||||
|
||||
// The three helpers below are routinely called as statements, to add a key to a document, so their
|
||||
// result is not `nodiscard`.
|
||||
|
||||
@@ -14,14 +14,14 @@ typedef enum FFPrintType: uint8_t {
|
||||
// Both `moduleName` and `moduleArgs` are optional and explicitly handled: a null `moduleName` is what
|
||||
// `--set-keyless` relies on, and a null `moduleArgs` (used by ffPrintError callers) skips the custom
|
||||
// key entirely. So neither takes `nonnull`.
|
||||
void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType);
|
||||
void ffPrintLogoAndKey(const char* moduleName, uint32_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType);
|
||||
// Same for `moduleArgs`; `numArgs` / `arguments` are only read when `moduleArgs` is non-null, so
|
||||
// `arguments` is not `nonnull(6)` either. At least one caller discards the result, so not `nodiscard`.
|
||||
bool ffPrintFormat(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments);
|
||||
bool ffPrintFormat(const char* moduleName, uint32_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, uint32_t numArgs, const FFformatarg* arguments);
|
||||
#define FF_PRINT_FORMAT_CHECKED(moduleName, moduleIndex, moduleArgs, printType, arguments) \
|
||||
ffPrintFormat((moduleName), (moduleIndex), (moduleArgs), (printType), (sizeof(arguments) / sizeof(*arguments)), (arguments));
|
||||
// `moduleName` / `moduleArgs` are forwarded to ffPrintLogoAndKey, which accepts null for both;
|
||||
// only `message` is dereferenced here (by `vprintf`).
|
||||
[[gnu::format(printf, 5, 6), gnu::nonnull(5)]] void ffPrintError(const char* moduleName, uint8_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...);
|
||||
[[gnu::format(printf, 5, 6), gnu::nonnull(5)]] void ffPrintError(const char* moduleName, uint32_t moduleIndex, const FFModuleArgs* moduleArgs, FFPrintType printType, const char* message, ...);
|
||||
[[gnu::nonnull(1)]] void ffPrintColor(const FFstrbuf* colorValue);
|
||||
void ffPrintCharTimes(char c, uint32_t times);
|
||||
|
||||
@@ -77,9 +77,9 @@ static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buff
|
||||
}
|
||||
|
||||
if (ffReadFileBufferRelative(subfd, "global_rsv_reserved", buffer)) {
|
||||
// global_rsv_reserved is already the used part of the reservation, global_rsv_size its total
|
||||
item->globalReservationUsed = ffStrbufToUInt(buffer, 0);
|
||||
}
|
||||
item->globalReservationUsed = item->globalReservationTotal - item->globalReservationUsed;
|
||||
|
||||
#define FF_BTRFS_DETECT_PROFILE(_index, _type, _profile, _copies) \
|
||||
else if (faccessat(subfd, _type "/" _profile "/", F_OK, 0) == 0) { \
|
||||
|
||||
@@ -37,6 +37,7 @@ const char* ffDetectCamera(FFlist* result)
|
||||
ffStrbufInitS(&camera->name, device.localizedName.UTF8String);
|
||||
ffStrbufInitS(&camera->vendor, device.manufacturer.UTF8String);
|
||||
ffStrbufInitS(&camera->id, device.uniqueID.UTF8String);
|
||||
ffStrbufInit(&camera->colorspace);
|
||||
switch (device.activeColorSpace)
|
||||
{
|
||||
case AVCaptureColorSpace_sRGB: ffStrbufInitStatic(&camera->colorspace, "sRGB"); break;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "camera.h"
|
||||
|
||||
const char* ffDetectCamera([[maybe_unused]] FFlist* result) {
|
||||
return "Not support on this platform";
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
|
||||
@@ -42,7 +42,13 @@ const char* ffDetectCommand(FFCommandOptions* options, FFstrbuf* result) {
|
||||
if (!options->parallel) {
|
||||
bundle.error = spawnProcess(options, &bundle.handle);
|
||||
} else if (!FF_LIST_SHIFT(commandQueue, &bundle)) {
|
||||
return "[BUG] command queue is empty";
|
||||
// The module was printed without having been prepared first, so there is no process to collect.
|
||||
// That is the expected outcome for a module gated on `condition.succeeded`: whether it is
|
||||
// printed at all is only known in the print pass, so the prepare pass skips it (see
|
||||
// `printJsonConfig`). In every other case the queue and the printed modules must line up.
|
||||
return "Module was not prepared, so it cannot run in parallel. Modules gated on "
|
||||
"`condition.succeeded` are never prepared, which is the expected cause; "
|
||||
"anything else is a bug in fastfetch, please report it";
|
||||
}
|
||||
|
||||
if (bundle.error) {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "cpu.h"
|
||||
|
||||
const char* ffDetectCPUImpl([[maybe_unused]] const FFCPUOptions* options, [[maybe_unused]] FFCPUResult* cpu) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "fastfetch.h"
|
||||
#include "detection/cpuusage/cpuusage.h"
|
||||
|
||||
const char* ffGetCpuUsageInfo([[maybe_unused]] FFlist* cpuTimes) {
|
||||
return "Not support on this platform";
|
||||
}
|
||||
@@ -248,6 +248,9 @@ const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOpti
|
||||
getKDE(result, options);
|
||||
} else if (ffStrbufEqualS(deName, FF_DE_PRETTY_GNOME)) {
|
||||
getGnome(result, options);
|
||||
} else if (ffStrbufEqualS(deName, FF_DE_PRETTY_GNOME_CLASSIC)) {
|
||||
// GNOME Classic is GNOME Shell in classic mode, so gnome-shell reports the same version
|
||||
getGnome(result, options);
|
||||
} else if (ffStrbufEqualS(deName, FF_DE_PRETTY_CINNAMON)) {
|
||||
getCinnamon(result, options);
|
||||
} else if (ffStrbufEqualS(deName, FF_DE_PRETTY_XFCE4)) {
|
||||
@@ -260,7 +263,7 @@ const char* ffDetectDEVersion(const FFstrbuf* deName, FFstrbuf* result, FFDEOpti
|
||||
getBudgie(result, options);
|
||||
} else if (ffStrbufEqualS(deName, FF_DE_PRETTY_UNITY)) {
|
||||
getUnity(result, options);
|
||||
} else if (ffStrbufEqualS(deName, "trinity")) {
|
||||
} else if (ffStrbufIgnCaseEqualS(deName, "trinity") || ffStrbufIgnCaseEqualS(deName, "tde")) {
|
||||
getTrinity(result, options);
|
||||
} else if (ffStrbufEqualS(deName, FF_DE_PRETTY_COSMIC)) {
|
||||
getCosmic(result, options);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "disk.h"
|
||||
|
||||
const char* ffDetectDisksImpl([[maybe_unused]] FFDiskOptions* options, [[maybe_unused]] FFlist* disks) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -46,13 +46,17 @@ const char* ffDetectDNS(FFDNSOptions* options, FFlist* results) {
|
||||
}
|
||||
|
||||
for (IP_ADAPTER_DNS_SERVER_ADDRESS_XP* ifa = adapter->FirstDnsServerAddress; ifa; ifa = ifa->Next) {
|
||||
FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results);
|
||||
// The element must only be added once it can be filled: `FF_LIST_ADD` does not zero the
|
||||
// new element, so an unrecognized address family used to leave an uninitialized FFstrbuf
|
||||
// in the list, which the callers then read and free
|
||||
if (ifa->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
SOCKADDR_IN* ipv4 = (SOCKADDR_IN*) ifa->Address.lpSockaddr;
|
||||
FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results);
|
||||
ffStrbufInitA(item, INET_ADDRSTRLEN);
|
||||
item->length = (uint32_t) (RtlIpv4AddressToStringA(&ipv4->sin_addr, item->chars) - item->chars);
|
||||
} else if (ifa->Address.lpSockaddr->sa_family == AF_INET6) {
|
||||
SOCKADDR_IN6* ipv6 = (SOCKADDR_IN6*) ifa->Address.lpSockaddr;
|
||||
FFstrbuf* item = FF_LIST_ADD(FFstrbuf, *results);
|
||||
ffStrbufInitA(item, INET6_ADDRSTRLEN);
|
||||
item->length = (uint32_t) (RtlIpv6AddressToStringA(&ipv6->sin6_addr, item->chars) - item->chars);
|
||||
}
|
||||
|
||||
@@ -32,17 +32,33 @@ static bool extractNanoVersionFromBinary(const char* str, [[maybe_unused]] uint3
|
||||
return false;
|
||||
}
|
||||
|
||||
// The message is printed to the user verbatim, so it has to name the variable that was wrong:
|
||||
// `"Executable not found"` from `ffFindExecutableInPath()` doesn't say what was searched for.
|
||||
static const char* notAnExecutable(bool isVisual) {
|
||||
return isVisual ? "$VISUAL does not point to an executable" : "$EDITOR does not point to an executable";
|
||||
}
|
||||
|
||||
const char* ffDetectEditor(FFEditorResult* result) {
|
||||
bool isVisual = true;
|
||||
ffStrbufSetS(&result->name, getenv("VISUAL"));
|
||||
if (result->name.length) {
|
||||
result->type = "Visual";
|
||||
} else {
|
||||
if (!result->name.length) {
|
||||
isVisual = false;
|
||||
ffStrbufSetS(&result->name, getenv("EDITOR"));
|
||||
if (result->name.length) {
|
||||
result->type = "Editor";
|
||||
} else {
|
||||
return "$VISUAL or $EDITOR not set";
|
||||
}
|
||||
}
|
||||
|
||||
if (!result->name.length) {
|
||||
return "$VISUAL or $EDITOR not set";
|
||||
}
|
||||
result->type = isVisual ? "Visual" : "Editor";
|
||||
|
||||
// `$VISUAL`/`$EDITOR` hold a shell command line rather than a bare name, so they routinely
|
||||
// carry arguments: `EDITOR="code -w"`, `EDITOR="vim -f"`. Only the first word names the
|
||||
// program. The whole string used to be handed to `ffFindExecutableInPath()`, which could never
|
||||
// match it, and the module then printed the unparsed value as if it had been resolved.
|
||||
ffStrbufSubstrBefore(&result->name, (uint32_t) strcspn(result->name.chars, " \t\n\v\f\r"));
|
||||
|
||||
if (result->name.length == 0) {
|
||||
return notAnExecutable(isVisual);
|
||||
}
|
||||
|
||||
if (ffIsAbsolutePath(result->name.chars)) {
|
||||
@@ -50,14 +66,19 @@ const char* ffDetectEditor(FFEditorResult* result) {
|
||||
} else {
|
||||
const char* error = ffFindExecutableInPath(result->name.chars, &result->path);
|
||||
if (error) {
|
||||
return nullptr;
|
||||
// Reporting success here is what made `EDITOR="code -w"` print the raw value: the module
|
||||
// reads `nullptr` as "detection succeeded" and then falls back to printing `name`.
|
||||
return notAnExecutable(isVisual);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
char buf[PATH_MAX + 1];
|
||||
if (!realpath(result->path.chars, buf)) {
|
||||
return nullptr;
|
||||
// An absolute `$VISUAL`/`$EDITOR` is copied into `path` before it is verified, so a path
|
||||
// that does not exist has to be reported here instead of being handed to the caller as
|
||||
// if it had been checked.
|
||||
return notAnExecutable(isVisual);
|
||||
}
|
||||
|
||||
// WIN32: Should we handle scoop shim exe here?
|
||||
@@ -77,11 +98,13 @@ const char* ffDetectEditor(FFEditorResult* result) {
|
||||
#endif
|
||||
);
|
||||
if (index == result->path.length) {
|
||||
return nullptr;
|
||||
// `realpath()` always yields an absolute path, so neither of these two is reachable in
|
||||
// practice. They used to `return nullptr`, which the module read as a success.
|
||||
return "Failed to determine the executable name";
|
||||
}
|
||||
ffStrbufSetS(&result->exe, &result->path.chars[index + 1]);
|
||||
if (!result->exe.length) {
|
||||
return nullptr;
|
||||
return "Failed to determine the executable name";
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -92,6 +115,7 @@ const char* ffDetectEditor(FFEditorResult* result) {
|
||||
}
|
||||
|
||||
if (!instance.config.general.detectVersion) {
|
||||
// Not an error: the editor was found, version probing was just turned off.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "fastfetch.h"
|
||||
#include "font.h"
|
||||
|
||||
const char* ffDetectFontImpl([[maybe_unused]] FFFontResult* result) {
|
||||
FF_UNUSED(result);
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -124,7 +124,7 @@ const char* detectByOpenGL(FFlist* gpus) {
|
||||
gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
|
||||
gpu->frequency = FF_GPU_FREQUENCY_UNSET;
|
||||
gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
|
||||
gpu->dedicated = gpu->shared = (FFGPUMemory) { 0, 0 };
|
||||
gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
|
||||
gpu->deviceId = 0;
|
||||
gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "gpu.h"
|
||||
|
||||
const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
|
||||
FF_UNUSED(options, gpus);
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "initsystem.h"
|
||||
|
||||
const char* ffDetectInitSystem([[maybe_unused]] FFInitSystemResult* result) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "keyboard.h"
|
||||
|
||||
const char* ffDetectKeyboard([[maybe_unused]] FFlist* devices /* List of FFKeyboardDevice */) {
|
||||
return "No mouse support on this platform";
|
||||
return "No keyboard support on this platform";
|
||||
}
|
||||
|
||||
@@ -18,16 +18,15 @@ const char* ffDetectLoadavg(double result[3]) {
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifndef __GNU__
|
||||
|
||||
// getloadavg requires higher ANDROID_API version
|
||||
struct sysinfo si;
|
||||
if (sysinfo(&si) < 0) {
|
||||
return "sysinfo() failed";
|
||||
return "both /proc/loadavg and sysinfo() failed";
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
result[i] = (double) si.loads[i] / (1 << SI_LOAD_SHIFT);
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ void ffDetectMediaImpl(FFMediaResult* media, bool saveCover);
|
||||
|
||||
static FFMediaResult result;
|
||||
|
||||
// The cover file of this run, if there is one. It is tracked here rather than in `result` because
|
||||
// the cached result is dropped between `--dynamic-interval` rounds, while the file has to stay on
|
||||
// disk for the whole run: the `kitty-direct` logo type lets the terminal open it by path, and the
|
||||
// logo is replayed from the line cache on every round.
|
||||
// The cover file of this run, if there is one. It is tracked here rather than in `result` because the
|
||||
// cached result is dropped between `--dynamic-interval` rounds, and `result.cover` — the only other
|
||||
// copy of the path — is destroyed with it. The file itself has to outlive that, since the
|
||||
// `kitty-direct` logo type hands its path to the terminal, so it is kept for the whole run and
|
||||
// removed at exit.
|
||||
static FFstrbuf coverFile;
|
||||
static bool coverFileRegistered = false;
|
||||
|
||||
@@ -71,6 +72,12 @@ const FFMediaResult* ffDetectMedia(bool saveCover) {
|
||||
ffStrbufTrimRightSpace(&result.player);
|
||||
|
||||
if (saveCover && result.removeCoverAfterUse) {
|
||||
// A new round can produce a new cover while the previous file is still on disk, and
|
||||
// `coverFile` is about to forget its path. Remove it here, otherwise only the last one
|
||||
// would ever be cleaned up by `atexit`.
|
||||
if (coverFile.length > 0 && !ffStrbufEqual(&coverFile, &result.cover)) {
|
||||
ffRemoveFile(coverFile.chars);
|
||||
}
|
||||
ffStrbufSet(&coverFile, &result.cover);
|
||||
if (!coverFileRegistered) {
|
||||
coverFileRegistered = true;
|
||||
|
||||
@@ -111,7 +111,14 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
}
|
||||
|
||||
#if !FF_MODULE_DISABLE_MEDIA
|
||||
[[gnu::visibility("default"), gnu::used]] int ffPrintMediaByMediaRemote(int saveCover) {
|
||||
// Called from the Perl helper process through `DynaLoader::dl_install_xsub`. That mechanism installs a
|
||||
// plain C function as an XSUB, so Perl calls it with the `CV*` in the first parameter slot and no Perl
|
||||
// argument ever reaches a parameter — a `bool saveCover` parameter read as a pointer and was always
|
||||
// truthy. The flag has to travel in the environment instead; see `getMediaByAuthorizedProcess`.
|
||||
[[gnu::visibility("default"), gnu::used]] int ffPrintMediaByMediaRemote(void) {
|
||||
const char* saveCoverEnv = getenv("FF_MEDIA_REMOTE_SAVE_COVER");
|
||||
bool saveCover = saveCoverEnv && saveCoverEnv[0] == '1';
|
||||
|
||||
FFMediaResult media = {
|
||||
.status = ffStrbufCreate(),
|
||||
.song = ffStrbufCreate(),
|
||||
@@ -121,7 +128,7 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
.player = ffStrbufCreate(),
|
||||
.cover = ffStrbufCreate(),
|
||||
};
|
||||
if (getMediaByMediaRemote(&media, !!saveCover) != nullptr) {
|
||||
if (getMediaByMediaRemote(&media, saveCover) != nullptr) {
|
||||
return 1;
|
||||
}
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
@@ -156,13 +163,17 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
|
||||
static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover) {
|
||||
// #1737
|
||||
FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("require DynaLoader;\
|
||||
// `DynaLoader::dl_install_xsub` hands the C function the `CV*` as its first parameter, so the flag
|
||||
// cannot be passed as a Perl argument — `ffPrintMediaByMediaRemote` reads it from the environment,
|
||||
// which the child process inherits.
|
||||
FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("$ENV{'FF_MEDIA_REMOTE_SAVE_COVER'}='%c';\
|
||||
require DynaLoader;\
|
||||
my $so = DynaLoader::dl_load_file('%s', 0);\
|
||||
my $sym = DynaLoader::dl_find_symbol($so, 'ffPrintMediaByMediaRemote');\
|
||||
DynaLoader::dl_install_xsub('fn', $sym, __FILE__);\
|
||||
exit(fn(%c))",
|
||||
instance.state.platform.exePath.chars,
|
||||
saveCover ? '1' : '0');
|
||||
exit(fn())",
|
||||
saveCover ? '1' : '0',
|
||||
instance.state.platform.exePath.chars);
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
|
||||
const char* error = ffProcessAppendStdOut(
|
||||
@@ -201,12 +212,17 @@ void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) {
|
||||
}
|
||||
if (error) {
|
||||
ffStrbufAppendS(&media->error, error);
|
||||
} else if (media->player.length == 0 && media->playerId.length > 0) {
|
||||
ffStrbufSet(&media->player, &media->playerId);
|
||||
if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) {
|
||||
ffStrbufSubstrAfter(&media->player, strlen("com.") - 1);
|
||||
} else {
|
||||
if (media->player.length == 0 && media->playerId.length > 0) {
|
||||
ffStrbufSet(&media->player, &media->playerId);
|
||||
if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) {
|
||||
ffStrbufSubstrAfter(&media->player, strlen("com.") - 1);
|
||||
}
|
||||
ffStrbufReplaceAllC(&media->player, '.', ' ');
|
||||
}
|
||||
ffStrbufReplaceAllC(&media->player, '.', ' ');
|
||||
// The cover is a temporary file that this process created, so it is ours to delete. This used
|
||||
// to be flagged only in the branch above, which meant the file was left behind on every run
|
||||
// whose player name was already known — that is, almost all of them.
|
||||
if (media->cover.length > 0) {
|
||||
media->removeCoverAfterUse = true;
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "media.h"
|
||||
|
||||
void ffDetectMediaImpl(FFMediaResult* media) {
|
||||
ffStrbufAppendS(&media->error, "Not supported on this platform");
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "memory.h"
|
||||
|
||||
const char* ffDetectMemory([[maybe_unused]] FFMemoryResult* ram) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
#include "packages.h"
|
||||
|
||||
void ffDetectPackagesImpl([[maybe_unused]] FFPackagesResult* result, [[maybe_unused]] FFPackagesOptions* options) {
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "processes.h"
|
||||
|
||||
const char* ffDetectProcesses([[maybe_unused]] const FFProcessesOptions* options, [[maybe_unused]] FFProcessesResult* result) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "swap.h"
|
||||
|
||||
const char* ffDetectSwap(FFSwapResult* swap) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#include "fastfetch.h"
|
||||
#include "users.h"
|
||||
|
||||
const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -250,7 +250,12 @@ static const char* detectVulkan(FFVulkanResult* result) {
|
||||
|
||||
ffStrbufInitS(&gpu->name, physicalDeviceProperties.properties.deviceName);
|
||||
|
||||
gpu->type = physicalDeviceProperties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? FF_GPU_TYPE_DISCRETE : FF_GPU_TYPE_INTEGRATED;
|
||||
switch (physicalDeviceProperties.properties.deviceType) {
|
||||
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: gpu->type = FF_GPU_TYPE_DISCRETE; break;
|
||||
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
||||
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: gpu->type = FF_GPU_TYPE_INTEGRATED; break; // Virtual machine?
|
||||
default: gpu->type = FF_GPU_TYPE_UNKNOWN; break;
|
||||
}
|
||||
ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString(physicalDeviceProperties.properties.vendorID));
|
||||
ffStrbufInitS(&gpu->driver, driverProperties.driverInfo);
|
||||
ffStrbufInit(&gpu->memoryType);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include "wallpaper.h"
|
||||
|
||||
const char* ffDetectWallpaper([[maybe_unused]] FFstrbuf* result) {
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "wifi.h"
|
||||
|
||||
const char* ffDetectWifi([[maybe_unused]] FFlist* result) {
|
||||
return "Not support on this platform";
|
||||
return "Not supported on this platform";
|
||||
}
|
||||
|
||||
@@ -252,9 +252,10 @@ const char* ffDetectWifi(FFlist* result) {
|
||||
connInfo->wlanAssociationAttributes.dot11BssType,
|
||||
connInfo->wlanSecurityAttributes.bSecurityEnabled,
|
||||
nullptr,
|
||||
&bssList) == ERROR_SUCCESS &&
|
||||
bssList->dwNumberOfItems > 0) {
|
||||
item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000);
|
||||
&bssList) == ERROR_SUCCESS) {
|
||||
if (bssList->dwNumberOfItems > 0) {
|
||||
item->conn.frequency = (uint16_t) (bssList->wlanBssEntries[0].ulChCenterFrequency / 1000);
|
||||
}
|
||||
ffWlanFreeMemory(bssList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "fastfetch.h"
|
||||
#include "wmtheme.h"
|
||||
|
||||
bool ffDetectWmTheme(FFstrbuf* themeOrError) {
|
||||
ffStrbufAppendS(themeOrError, "Not supported on this platform");
|
||||
return false;
|
||||
}
|
||||
+1
-1
@@ -602,7 +602,7 @@ static void enableJsonOutput(FFdata* data) {
|
||||
}
|
||||
|
||||
static void genConfigCommon(FFdata* data, const char* value) {
|
||||
if (!getenv("NO_COLOR") && isatty(STDOUT_FILENO) && isatty(STDIN_FILENO)
|
||||
if (!getenv("NO_COLOR") && ffIsTerminal(STDOUT_FILENO) && ffIsTerminal(STDIN_FILENO)
|
||||
#ifdef _WIN32
|
||||
&& ffIsWindows10OrGreater()
|
||||
#endif
|
||||
|
||||
@@ -345,5 +345,5 @@ FFModuleBaseInfo ffBatteryModuleInfo = {
|
||||
{ "Battery time remaining seconds", "time-seconds" },
|
||||
{ "Battery time remaining (formatted)", "time-formatted" },
|
||||
})),
|
||||
.defaultOrder = 44,
|
||||
.defaultOrder = 45,
|
||||
};
|
||||
|
||||
@@ -71,7 +71,7 @@ bool ffPrintBluetooth(FFBluetoothOptions* options) {
|
||||
|
||||
uint8_t i = 1;
|
||||
FF_LIST_FOR_EACH (FFBluetoothResult, device, devices) {
|
||||
printDevice(options, device, devices.length == 0 ? 0 : i);
|
||||
printDevice(options, device, devices.length == 1 ? 0 : i);
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -189,5 +189,5 @@ FFModuleBaseInfo ffBluetoothModuleInfo = {
|
||||
{ "Is connected", "connected" },
|
||||
{ "Battery percentage bar", "battery-percentage-bar" },
|
||||
})),
|
||||
.defaultOrder = 58,
|
||||
.defaultOrder = 59,
|
||||
};
|
||||
|
||||
@@ -71,6 +71,18 @@ static void printDevice(FFBluetoothRadioOptions* options, const FFBluetoothRadio
|
||||
case 13:
|
||||
version = "5.4";
|
||||
break;
|
||||
case 14:
|
||||
version = "6.0";
|
||||
break;
|
||||
case 15:
|
||||
version = "6.1";
|
||||
break;
|
||||
case 16:
|
||||
version = "6.2";
|
||||
break;
|
||||
case 17:
|
||||
version = "6.3";
|
||||
break;
|
||||
}
|
||||
|
||||
if (options->moduleArgs.outputFormat.length == 0) {
|
||||
@@ -229,5 +241,5 @@ FFModuleBaseInfo ffBluetoothRadioModuleInfo = {
|
||||
{ "Discoverable", "discoverable" },
|
||||
{ "Connectable / Pairable", "connectable" },
|
||||
})),
|
||||
.defaultOrder = 59,
|
||||
.defaultOrder = 60,
|
||||
};
|
||||
|
||||
@@ -55,5 +55,5 @@ FFModuleBaseInfo ffBreakModuleInfo = {
|
||||
.destroyOptions = (void*) ffDestroyBreakOptions,
|
||||
.parseJsonObject = (void*) ffParseBreakJsonObject,
|
||||
.printModule = (void*) ffPrintBreak,
|
||||
.defaultOrder = 71,
|
||||
.defaultOrder = 72,
|
||||
};
|
||||
|
||||
@@ -168,6 +168,9 @@ bool ffGenerateBtrfsJsonResult([[maybe_unused]] FFBtrfsOptions* options, yyjson_
|
||||
yyjson_mut_obj_add_uint(doc, obj, "nodeSize", btrfs->nodeSize);
|
||||
yyjson_mut_obj_add_uint(doc, obj, "sectorSize", btrfs->sectorSize);
|
||||
yyjson_mut_obj_add_uint(doc, obj, "totalSize", btrfs->totalSize);
|
||||
yyjson_mut_val* globalReservation = yyjson_mut_obj_add_obj(doc, obj, "globalReservation");
|
||||
yyjson_mut_obj_add_uint(doc, globalReservation, "used", btrfs->globalReservationUsed);
|
||||
yyjson_mut_obj_add_uint(doc, globalReservation, "total", btrfs->globalReservationTotal);
|
||||
yyjson_mut_val* allocation = yyjson_mut_obj_add_arr(doc, obj, "allocation");
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(btrfs->allocation); ++i) {
|
||||
yyjson_mut_val* item = yyjson_mut_arr_add_obj(doc, allocation);
|
||||
@@ -244,5 +247,5 @@ FFModuleBaseInfo ffBtrfsModuleInfo = {
|
||||
{ "Node size", "node-size" },
|
||||
{ "Sector size", "sector-size" },
|
||||
})),
|
||||
.defaultOrder = 42,
|
||||
.defaultOrder = 43,
|
||||
};
|
||||
|
||||
@@ -153,5 +153,5 @@ FFModuleBaseInfo ffCameraModuleInfo = {
|
||||
{ "Width (in px)", "width" },
|
||||
{ "Height (in px)", "height" },
|
||||
})),
|
||||
.defaultOrder = 61,
|
||||
.defaultOrder = 62,
|
||||
};
|
||||
|
||||
@@ -288,5 +288,5 @@ FFModuleBaseInfo ffCodecModuleInfo = {
|
||||
{ "Compatibility alias of codec types", "types" },
|
||||
{ "Platform API used for detection", "platform-api" },
|
||||
})),
|
||||
.defaultOrder = 37,
|
||||
.defaultOrder = 38,
|
||||
};
|
||||
|
||||
+20
-10
@@ -172,7 +172,9 @@ void ffParseColorsJsonObject(FFColorsOptions* options, yyjson_val* module) {
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "paddingLeft")) {
|
||||
options->paddingLeft = (uint32_t) yyjson_get_uint(val);
|
||||
if (!ffJsonConfigParseUInt32(val, &options->paddingLeft, UINT32_MAX)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Colors), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property 'paddingLeft' must be a non-negative integer no greater than 4294967295");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -182,7 +184,13 @@ void ffParseColorsJsonObject(FFColorsOptions* options, yyjson_val* module) {
|
||||
} else {
|
||||
yyjson_val* width = yyjson_obj_get(val, "width");
|
||||
if (width) {
|
||||
options->block.width = (uint8_t) yyjson_get_uint(width);
|
||||
uint32_t value;
|
||||
// A width of 0 or more than 9 is not useful: it makes the block either invisible or unreadably long
|
||||
if (!ffJsonConfigParseUInt32(width, &value, 9) || value == 0) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Colors), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property 'block.width' must be an integer between 1 and 9");
|
||||
} else {
|
||||
options->block.width = (uint8_t) value;
|
||||
}
|
||||
}
|
||||
|
||||
yyjson_val* range = yyjson_obj_get(val, "range");
|
||||
@@ -190,15 +198,17 @@ void ffParseColorsJsonObject(FFColorsOptions* options, yyjson_val* module) {
|
||||
if (!yyjson_is_arr(range) || yyjson_arr_size(range) != 2) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Colors), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s.range value: must be an array of 2 elements", unsafe_yyjson_get_str(key));
|
||||
} else {
|
||||
uint8_t start = (uint8_t) yyjson_get_uint(yyjson_arr_get(range, 0));
|
||||
uint8_t end = (uint8_t) yyjson_get_uint(yyjson_arr_get(range, 1));
|
||||
if (start > end) {
|
||||
uint32_t start, end;
|
||||
// Both elements must be integers in [0, 15]. `yyjson_get_uint()` used to read
|
||||
// anything else as 0, so `[1.5, 3]` silently printed the same as `[0, 3]`
|
||||
if (!ffJsonConfigParseUInt32(yyjson_arr_get(range, 0), &start, 15) ||
|
||||
!ffJsonConfigParseUInt32(yyjson_arr_get(range, 1), &end, 15)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Colors), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property '%s.range' must be an array of two integers between 0 and 15", unsafe_yyjson_get_str(key));
|
||||
} else if (start > end) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Colors), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s.range value: range[0] > range[1]", unsafe_yyjson_get_str(key));
|
||||
} else if (end > 15) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Colors), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Invalid %s.range value: range[1] > 15", unsafe_yyjson_get_str(key));
|
||||
} else {
|
||||
options->block.range[0] = start;
|
||||
options->block.range[1] = end;
|
||||
options->block.range[0] = (uint8_t) start;
|
||||
options->block.range[1] = (uint8_t) end;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,5 +334,5 @@ FFModuleBaseInfo ffColorsModuleInfo = {
|
||||
.parseJsonObject = (void*) ffParseColorsJsonObject,
|
||||
.printModule = (void*) ffPrintColors,
|
||||
.generateJsonConfig = (void*) ffGenerateColorsJsonConfig,
|
||||
.defaultOrder = 72,
|
||||
.defaultOrder = 73,
|
||||
};
|
||||
|
||||
@@ -3,6 +3,23 @@
|
||||
#include "modules/command/command.h"
|
||||
#include "detection/command/command.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
// `ffStrbufGetline()` splits on `\n` only, so a CRLF line keeps its trailing `\r`. That is what every
|
||||
// `cmd.exe` builtin and most native Windows tools produce, and the leftover carriage return makes the
|
||||
// terminal jump back to the start of the line. The `\r` is overwritten with a NUL, so `line` stays a
|
||||
// usable C string; the length without the `\r` is returned for callers that need to copy the line.
|
||||
//
|
||||
// `len` itself must stay the distance from the line start to the delimiter: `ffStrbufGetdelim()`
|
||||
// advances with `*lineptr += *n`, so shortening it would make the next call start inside this line.
|
||||
static size_t trimCarriageReturn(char* line, size_t len) {
|
||||
if (len > 0 && line[len - 1] == '\r') {
|
||||
line[len - 1] = '\0';
|
||||
return len - 1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ffPrintCommand(FFCommandOptions* options) {
|
||||
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
|
||||
const char* error = ffDetectCommand(options, &result);
|
||||
@@ -18,10 +35,13 @@ bool ffPrintCommand(FFCommandOptions* options) {
|
||||
}
|
||||
|
||||
if (options->splitLines) {
|
||||
uint8_t index = 0;
|
||||
uint32_t index = 0;
|
||||
char* line = nullptr;
|
||||
size_t len = 0;
|
||||
while (ffStrbufGetline(&line, &len, &result)) {
|
||||
#if defined(_WIN32)
|
||||
trimCarriageReturn(line, len);
|
||||
#endif
|
||||
if (options->moduleArgs.outputFormat.length == 0) {
|
||||
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Command), ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
|
||||
puts(line);
|
||||
@@ -113,7 +133,14 @@ bool ffGenerateCommandJsonResult([[maybe_unused]] FFCommandOptions* options, yyj
|
||||
char* line = nullptr;
|
||||
size_t len = 0;
|
||||
while (ffStrbufGetline(&line, &len, &result)) {
|
||||
yyjson_mut_arr_add_strncpy(doc, jsonArray, line, len);
|
||||
// `len` has to stay the distance to the delimiter (see `trimCarriageReturn`), so the
|
||||
// possibly shorter length of the line itself is tracked separately.
|
||||
#if defined(_WIN32)
|
||||
size_t contentLen = trimCarriageReturn(line, len);
|
||||
#else
|
||||
size_t contentLen = len;
|
||||
#endif
|
||||
yyjson_mut_arr_add_strncpy(doc, jsonArray, line, contentLen);
|
||||
}
|
||||
} else {
|
||||
yyjson_mut_obj_add_strbuf(doc, module, "result", &result);
|
||||
|
||||
@@ -129,7 +129,10 @@ void ffParseCPUUsageJsonObject(FFCPUUsageOptions* options, yyjson_val* module) {
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "waitTime")) {
|
||||
options->waitTime = (uint32_t) yyjson_get_uint(val);
|
||||
if (!ffJsonConfigParseUInt32(val, &options->waitTime, UINT32_MAX)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(CPUUsage), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Property 'waitTime' must be a non-negative integer no greater than 4294967295");
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,25 @@ void ffGenerateCustomJsonConfig(FFCustomOptions* options, yyjson_mut_doc* doc, y
|
||||
ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs);
|
||||
}
|
||||
|
||||
bool ffGenerateCustomJsonResult(FFCustomOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
|
||||
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
|
||||
|
||||
// This module's output *is* its format string, so the JSON result is that string rendered —
|
||||
// for a `qjs:`/`lua:` format that means the script's output, not the script. `ffPrintCustom()`
|
||||
// gets the same value through `ffPrintFormat()`. Without this the module had no JSON result at
|
||||
// all, so a script consuming `--format json` could not read a custom line's value.
|
||||
if (!ffParseFormatString(&result, &options->moduleArgs.outputFormat, 0, (FFformatarg[]) {})) {
|
||||
// `yyjson_mut_obj_add_str()` only copies strings that need escaping; for anything else it
|
||||
// stores the pointer as-is. `result` is freed when this function returns, long before the
|
||||
// document is serialised, so the error has to be copied into the document explicitly.
|
||||
yyjson_mut_obj_add_strcpy(doc, module, "error", result.chars);
|
||||
return false;
|
||||
}
|
||||
|
||||
yyjson_mut_obj_add_strbuf(doc, module, "result", &result);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ffParseCustomJsonObject(FFCustomOptions* options, yyjson_val* module) {
|
||||
yyjson_val *key, *val;
|
||||
size_t idx, max;
|
||||
@@ -63,5 +82,6 @@ FFModuleBaseInfo ffCustomModuleInfo = {
|
||||
.destroyOptions = (void*) ffDestroyCustomOptions,
|
||||
.parseJsonObject = (void*) ffParseCustomJsonObject,
|
||||
.printModule = (void*) ffPrintCustom,
|
||||
.generateJsonResult = (void*) ffGenerateCustomJsonResult,
|
||||
.generateJsonConfig = (void*) ffGenerateCustomJsonConfig,
|
||||
};
|
||||
|
||||
@@ -48,13 +48,13 @@ static void printDateTimeFormat(struct tm* tm, const FFModuleArgs* moduleArgs) {
|
||||
result.week = (uint8_t) (tm->tm_yday / 7 + 1);
|
||||
strftime(result.weekday, sizeof(result.weekday), "%A", tm);
|
||||
strftime(result.weekdayShort, sizeof(result.weekdayShort), "%a", tm);
|
||||
result.dayInYear = (uint8_t) (tm->tm_yday + 1);
|
||||
result.dayInYear = (uint16_t) (tm->tm_yday + 1);
|
||||
result.dayInMonth = (uint8_t) tm->tm_mday;
|
||||
result.dayInWeek = tm->tm_wday == 0 ? 7 : (uint8_t) tm->tm_wday;
|
||||
strftime(result.dayPretty, sizeof(result.dayPretty), "%d", tm);
|
||||
result.hour = (uint8_t) tm->tm_hour;
|
||||
strftime(result.hourPretty, sizeof(result.hourPretty), "%H", tm);
|
||||
result.hour12 = (uint8_t) (result.hour % 12);
|
||||
result.hour12 = (uint8_t) (result.hour % 12 ?: 12);
|
||||
strftime(result.hour12Pretty, sizeof(result.hour12Pretty), "%I", tm);
|
||||
result.minute = (uint8_t) tm->tm_min;
|
||||
strftime(result.minutePretty, sizeof(result.minutePretty), "%M", tm);
|
||||
@@ -206,5 +206,5 @@ FFModuleBaseInfo ffDateTimeModuleInfo = {
|
||||
{ "Day in month with leading zero", "day-pretty" },
|
||||
{ "AM or PM", "am-pm" },
|
||||
})),
|
||||
.defaultOrder = 52,
|
||||
.defaultOrder = 53,
|
||||
};
|
||||
|
||||
@@ -510,5 +510,5 @@ FFModuleBaseInfo ffDiskModuleInfo = {
|
||||
{ "Size free", "size-free" },
|
||||
{ "Size available", "size-available" },
|
||||
})),
|
||||
.defaultOrder = 41,
|
||||
.defaultOrder = 42,
|
||||
};
|
||||
|
||||
@@ -111,7 +111,10 @@ void ffParseDiskIOJsonObject(FFDiskIOOptions* options, yyjson_val* module) {
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "waitTime")) {
|
||||
options->waitTime = (uint32_t) yyjson_get_uint(val);
|
||||
if (!ffJsonConfigParseUInt32(val, &options->waitTime, UINT32_MAX)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(DiskIO), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Property 'waitTime' must be a non-negative integer no greater than 4294967295");
|
||||
continue;
|
||||
}
|
||||
if (options->waitTime == 0) {
|
||||
options->waitTime = 1;
|
||||
}
|
||||
@@ -214,5 +217,5 @@ FFModuleBaseInfo ffDiskIOModuleInfo = {
|
||||
{ "Number of reads", "read-count" },
|
||||
{ "Number of writes", "write-count" },
|
||||
})),
|
||||
.defaultOrder = 67,
|
||||
.defaultOrder = 68,
|
||||
};
|
||||
|
||||
@@ -7,31 +7,50 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
static int sortByNameAsc(FFDisplayResult* a, FFDisplayResult* b) {
|
||||
return ffStrbufComp(&a->name, &b->name);
|
||||
static int sortByNameAsc(const void* a, const void* b) {
|
||||
return ffStrbufComp(&((const FFDisplayResult*) a)->name, &((const FFDisplayResult*) b)->name);
|
||||
}
|
||||
|
||||
static int sortByNameDesc(FFDisplayResult* a, FFDisplayResult* b) {
|
||||
return -ffStrbufComp(&a->name, &b->name);
|
||||
static int sortByNameDesc(const void* a, const void* b) {
|
||||
return -ffStrbufComp(&((const FFDisplayResult*) a)->name, &((const FFDisplayResult*) b)->name);
|
||||
}
|
||||
|
||||
bool ffPrintDisplay(FFDisplayOptions* options) {
|
||||
const FFDisplayServerResult* dsResult = ffConnectDisplayServer();
|
||||
|
||||
if (dsResult->displays.length == 0) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Couldn't detect display");
|
||||
return false;
|
||||
// `ffConnectDisplayServer()` hands out a process-wide result that other modules read too: `Monitor`
|
||||
// prints the same list, and `Brightness` pairs its own readings with these entries by position. It
|
||||
// must therefore not be reordered in place, which is what this module used to do through a cast
|
||||
// that dropped `const`. Sort a copy instead, so both the text and the JSON path get the requested
|
||||
// order without any other consumer seeing it.
|
||||
//
|
||||
// The copy is shallow: the entries keep owning their `FFstrbuf`s through the original result, and
|
||||
// it is never destroyed element-wise, so only the array itself is freed.
|
||||
static FFlist getOrderedDisplays(const FFDisplayOptions* options) {
|
||||
const FFlist* displays = &ffConnectDisplayServer()->displays;
|
||||
FFlist result = ffListCreateA(sizeof(FFDisplayResult), displays->length);
|
||||
if (displays->length > 0) {
|
||||
memcpy(result.data, displays->data, (size_t) displays->length * sizeof(FFDisplayResult));
|
||||
result.length = displays->length;
|
||||
}
|
||||
|
||||
if (options->order != FF_DISPLAY_ORDER_NONE) {
|
||||
ffListSort((FFlist*) &dsResult->displays, sizeof(FFDisplayResult), (void*) (options->order == FF_DISPLAY_ORDER_ASC ? sortByNameAsc : sortByNameDesc));
|
||||
ffListSort(&result, sizeof(FFDisplayResult), options->order == FF_DISPLAY_ORDER_ASC ? sortByNameAsc : sortByNameDesc);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ffPrintDisplay(FFDisplayOptions* options) {
|
||||
FF_LIST_AUTO_DESTROY displays = getOrderedDisplays(options);
|
||||
|
||||
if (displays.length == 0) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Couldn't detect display");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options->compactType != FF_DISPLAY_COMPACT_TYPE_NONE) {
|
||||
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Display), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
FF_LIST_FOR_EACH (FFDisplayResult, result, dsResult->displays) {
|
||||
FF_LIST_FOR_EACH (FFDisplayResult, result, displays) {
|
||||
if (options->compactType & FF_DISPLAY_COMPACT_TYPE_ORIGINAL_BIT) {
|
||||
ffStrbufAppendF(&buffer, "%ix%i", result->width, result->height);
|
||||
} else {
|
||||
@@ -42,7 +61,7 @@ bool ffPrintDisplay(FFDisplayOptions* options) {
|
||||
|
||||
if (options->compactType & FF_DISPLAY_COMPACT_TYPE_REFRESH_RATE_BIT) {
|
||||
if (result->refreshRate > 0) {
|
||||
const char* space = instance.config.display.freqSpaceBeforeUnit == FF_SPACE_BEFORE_UNIT_ALWAYS ? " " : "";
|
||||
const char* space = instance.config.display.freqSpaceBeforeUnit != FF_SPACE_BEFORE_UNIT_NEVER ? " " : "";
|
||||
if (options->preciseRefreshRate) {
|
||||
ffStrbufAppendF(&buffer, " @ %g%sHz", result->refreshRate, space);
|
||||
} else {
|
||||
@@ -62,9 +81,9 @@ bool ffPrintDisplay(FFDisplayOptions* options) {
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY key = ffStrbufCreate();
|
||||
|
||||
for (uint32_t i = 0; i < dsResult->displays.length; i++) {
|
||||
FFDisplayResult* result = FF_LIST_GET(FFDisplayResult, dsResult->displays, i);
|
||||
uint32_t moduleIndex = dsResult->displays.length == 1 ? 0 : i + 1;
|
||||
for (uint32_t i = 0; i < displays.length; i++) {
|
||||
FFDisplayResult* result = FF_LIST_GET(FFDisplayResult, displays, i);
|
||||
uint32_t moduleIndex = displays.length == 1 ? 0 : i + 1;
|
||||
const char* displayType = result->type == FF_DISPLAY_TYPE_UNKNOWN ? nullptr : result->type == FF_DISPLAY_TYPE_BUILTIN ? "Built-in"
|
||||
: "External";
|
||||
|
||||
@@ -295,16 +314,16 @@ void ffGenerateDisplayJsonConfig(FFDisplayOptions* options, yyjson_mut_doc* doc,
|
||||
}
|
||||
}
|
||||
|
||||
bool ffGenerateDisplayJsonResult([[maybe_unused]] FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
|
||||
const FFDisplayServerResult* dsResult = ffConnectDisplayServer();
|
||||
bool ffGenerateDisplayJsonResult(FFDisplayOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
|
||||
FF_LIST_AUTO_DESTROY displays = getOrderedDisplays(options);
|
||||
|
||||
if (dsResult->displays.length == 0) {
|
||||
if (displays.length == 0) {
|
||||
yyjson_mut_obj_add_str(doc, module, "error", "Couldn't detect display");
|
||||
return false;
|
||||
}
|
||||
|
||||
yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
|
||||
FF_LIST_FOR_EACH (FFDisplayResult, item, dsResult->displays) {
|
||||
FF_LIST_FOR_EACH (FFDisplayResult, item, displays) {
|
||||
yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
|
||||
yyjson_mut_obj_add_uint(doc, obj, "id", item->id);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "name", &item->name);
|
||||
|
||||
@@ -167,5 +167,5 @@ FFModuleBaseInfo ffDNSModuleInfo = {
|
||||
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
|
||||
{ "DNS result", "result" },
|
||||
})),
|
||||
.defaultOrder = 50,
|
||||
.defaultOrder = 51,
|
||||
};
|
||||
|
||||
@@ -5,6 +5,17 @@
|
||||
#include "detection/editor/editor.h"
|
||||
#include "modules/editor/editor.h"
|
||||
|
||||
// `FFEditorResult` owns four buffers and has no destructor of its own. Both entry points below
|
||||
// have to free them on the error path too — which used to be skipped, and which became the common
|
||||
// path once `ffDetectEditor()` started reporting an `$EDITOR` that does not name an executable
|
||||
// instead of quietly succeeding.
|
||||
static void destroyEditorResult(FFEditorResult* result) {
|
||||
ffStrbufDestroy(&result->name);
|
||||
ffStrbufDestroy(&result->path);
|
||||
ffStrbufDestroy(&result->exe);
|
||||
ffStrbufDestroy(&result->version);
|
||||
}
|
||||
|
||||
bool ffPrintEditor(FFEditorOptions* options) {
|
||||
FFEditorResult result = {
|
||||
.type = "Unknown",
|
||||
@@ -17,6 +28,7 @@ bool ffPrintEditor(FFEditorOptions* options) {
|
||||
|
||||
if (error) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Editor), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error);
|
||||
destroyEditorResult(&result);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -41,10 +53,7 @@ bool ffPrintEditor(FFEditorOptions* options) {
|
||||
}));
|
||||
}
|
||||
|
||||
ffStrbufDestroy(&result.name);
|
||||
ffStrbufDestroy(&result.path);
|
||||
ffStrbufDestroy(&result.exe);
|
||||
ffStrbufDestroy(&result.version);
|
||||
destroyEditorResult(&result);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -76,6 +85,7 @@ bool ffGenerateEditorJsonResult([[maybe_unused]] FFEditorOptions* options, yyjso
|
||||
|
||||
if (error) {
|
||||
yyjson_mut_obj_add_str(doc, module, "error", error);
|
||||
destroyEditorResult(&result);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,10 +96,7 @@ bool ffGenerateEditorJsonResult([[maybe_unused]] FFEditorOptions* options, yyjso
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "exe", &result.exe);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "version", &result.version);
|
||||
|
||||
ffStrbufDestroy(&result.name);
|
||||
ffStrbufDestroy(&result.path);
|
||||
ffStrbufDestroy(&result.exe);
|
||||
ffStrbufDestroy(&result.version);
|
||||
destroyEditorResult(&result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,11 +88,13 @@ bool ffPrintGamepad(FFGamepadOptions* options) {
|
||||
FFGamepadDevice* device = *pdevice;
|
||||
printDevice(options, device, filtered.length > 1 ? ++index : 0);
|
||||
}
|
||||
}
|
||||
|
||||
FF_LIST_FOR_EACH (FFGamepadDevice, device, result) {
|
||||
ffStrbufDestroy(&device->serial);
|
||||
ffStrbufDestroy(&device->name);
|
||||
}
|
||||
// `filtered` only holds borrowed pointers, so the strings of all devices — including the ignored
|
||||
// ones — have to be released here, not in the printing branch
|
||||
FF_LIST_FOR_EACH (FFGamepadDevice, device, result) {
|
||||
ffStrbufDestroy(&device->serial);
|
||||
ffStrbufDestroy(&device->name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -153,6 +155,8 @@ bool ffGenerateGamepadJsonResult([[maybe_unused]] FFGamepadOptions* options, yyj
|
||||
yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "serial", &device->serial);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "name", &device->name);
|
||||
// 0 means "the device does not report a battery level", not "empty battery"
|
||||
yyjson_mut_obj_add_uint(doc, obj, "battery", device->battery);
|
||||
|
||||
bool ignored = false;
|
||||
FF_LIST_FOR_EACH (FFstrbuf, ignore, options->ignores) {
|
||||
@@ -225,5 +229,5 @@ FFModuleBaseInfo ffGamepadModuleInfo = {
|
||||
{ "Battery percentage num", "battery-percentage" },
|
||||
{ "Battery percentage bar", "battery-percentage-bar" },
|
||||
})),
|
||||
.defaultOrder = 62,
|
||||
.defaultOrder = 63,
|
||||
};
|
||||
|
||||
+22
-16
@@ -11,6 +11,21 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// `hideType` is a filter, and FF_GPU_TYPE_NONE means "hide nothing" -- no detected GPU ever has
|
||||
// that type. Both the text and the JSON path must apply it, or the two disagree about which GPUs
|
||||
// exist (see bug.md B29).
|
||||
static bool gpuIsHidden(const FFGPUOptions* options, const FFGPUResult* gpu) {
|
||||
return options->hideType != FF_GPU_TYPE_NONE && gpu->type == options->hideType;
|
||||
}
|
||||
|
||||
static void selectGPUs(const FFGPUOptions* options, const FFlist* gpus, FFlist* selectedGPUs) {
|
||||
FF_LIST_FOR_EACH (FFGPUResult, gpu, *gpus) {
|
||||
if (!gpuIsHidden(options, gpu)) {
|
||||
*FF_LIST_ADD(const FFGPUResult*, *selectedGPUs) = gpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void printGPUResult(FFGPUOptions* options, uint8_t index, const FFGPUResult* gpu) {
|
||||
const char* type;
|
||||
switch (gpu->type) {
|
||||
@@ -182,21 +197,7 @@ bool ffPrintGPU(FFGPUOptions* options) {
|
||||
|
||||
FF_LIST_AUTO_DESTROY selectedGPUs = ffListCreateA(sizeof(const FFGPUResult*), gpus.length);
|
||||
|
||||
FF_LIST_FOR_EACH (FFGPUResult, gpu, gpus) {
|
||||
if (gpu->type == FF_GPU_TYPE_UNKNOWN && options->hideType == FF_GPU_TYPE_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (gpu->type == FF_GPU_TYPE_INTEGRATED && options->hideType == FF_GPU_TYPE_INTEGRATED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (gpu->type == FF_GPU_TYPE_DISCRETE && options->hideType == FF_GPU_TYPE_DISCRETE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*FF_LIST_ADD(const FFGPUResult*, selectedGPUs) = gpu;
|
||||
}
|
||||
selectGPUs(options, &gpus, &selectedGPUs);
|
||||
|
||||
uint32_t i = 0;
|
||||
FF_LIST_FOR_EACH (const FFGPUResult*, pgpu, selectedGPUs) {
|
||||
@@ -339,8 +340,13 @@ bool ffGenerateGPUJsonResult(FFGPUOptions* options, yyjson_mut_doc* doc, yyjson_
|
||||
return false;
|
||||
}
|
||||
|
||||
FF_LIST_AUTO_DESTROY selectedGPUs = ffListCreateA(sizeof(const FFGPUResult*), gpus.length);
|
||||
|
||||
selectGPUs(options, &gpus, &selectedGPUs);
|
||||
|
||||
yyjson_mut_val* arr = yyjson_mut_obj_add_arr(doc, module, "result");
|
||||
FF_LIST_FOR_EACH (FFGPUResult, gpu, gpus) {
|
||||
FF_LIST_FOR_EACH (const FFGPUResult*, pgpu, selectedGPUs) {
|
||||
const FFGPUResult* gpu = *pgpu;
|
||||
yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr);
|
||||
|
||||
if (gpu->index != FF_GPU_INDEX_UNSET) {
|
||||
|
||||
@@ -97,7 +97,6 @@ FFModuleBaseInfo ffKernelModuleInfo = {
|
||||
{ "Release", "release" },
|
||||
{ "Version", "version" },
|
||||
{ "Architecture", "arch" },
|
||||
{ "Display version", "display-version" },
|
||||
{ "Page size", "page-size" },
|
||||
})),
|
||||
.defaultOrder = 9,
|
||||
|
||||
@@ -54,13 +54,17 @@ bool ffPrintKeyboard(FFKeyboardOptions* options) {
|
||||
} else {
|
||||
uint8_t index = 0;
|
||||
FF_LIST_FOR_EACH (FFKeyboardDevice*, pdevice, filtered) {
|
||||
FFKeyboardDevice* device = *pdevice;
|
||||
printDevice(options, device, filtered.length > 1 ? ++index : 0);
|
||||
ffStrbufDestroy(&device->serial);
|
||||
ffStrbufDestroy(&device->name);
|
||||
printDevice(options, *pdevice, filtered.length > 1 ? ++index : 0);
|
||||
}
|
||||
}
|
||||
|
||||
// `filtered` only holds borrowed pointers, so the strings of every device — including the
|
||||
// ignored ones and the ones skipped when every device is ignored — have to be released here
|
||||
FF_LIST_FOR_EACH (FFKeyboardDevice, device, result) {
|
||||
ffStrbufDestroy(&device->serial);
|
||||
ffStrbufDestroy(&device->name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -183,5 +187,5 @@ FFModuleBaseInfo ffKeyboardModuleInfo = {
|
||||
{ "Name", "name" },
|
||||
{ "Serial number", "serial" },
|
||||
})),
|
||||
.defaultOrder = 64,
|
||||
.defaultOrder = 65,
|
||||
};
|
||||
|
||||
@@ -92,7 +92,14 @@ void ffParseLoadavgJsonObject(FFLoadavgOptions* options, yyjson_val* module) {
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "ndigits")) {
|
||||
options->ndigits = (uint8_t) yyjson_get_uint(val);
|
||||
// The schema documents 0..9 and the value ends up as the precision of `printf("%.*f")`,
|
||||
// so a wider value is not truncated to fit `uint8_t` silently any more.
|
||||
uint32_t ndigits = 0;
|
||||
if (!ffJsonConfigParseUInt32(val, &ndigits, 9)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Loadavg), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Property 'ndigits' must be an integer between 0 and 9");
|
||||
continue;
|
||||
}
|
||||
options->ndigits = (uint8_t) ndigits;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,5 +100,5 @@ FFModuleBaseInfo ffLocaleModuleInfo = {
|
||||
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
|
||||
{ "Locale code", "result" },
|
||||
})),
|
||||
.defaultOrder = 53,
|
||||
.defaultOrder = 54,
|
||||
};
|
||||
|
||||
@@ -112,6 +112,9 @@ bool ffPrintLocalIp(FFLocalIpOptions* options) {
|
||||
ffListSort(&results, sizeof(FFLocalIpResult), (const void*) sortIps);
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
// `{is-default-route}` reports the address families the interface carries the default route for
|
||||
// ("ipv4", "ipv6" or "ipv4,ipv6") instead of exposing the internal bit field as a number.
|
||||
FF_STRBUF_AUTO_DESTROY defaultRoute = ffStrbufCreate();
|
||||
|
||||
if (options->showType & FF_LOCALIP_TYPE_COMPACT_BIT) {
|
||||
ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(LocalIP), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT);
|
||||
@@ -138,12 +141,22 @@ bool ffPrintLocalIp(FFLocalIpOptions* options) {
|
||||
if (ip->speed > 0) {
|
||||
appendSpeed(ip, &buffer);
|
||||
}
|
||||
ffStrbufClear(&defaultRoute);
|
||||
if (ip->defaultRoute & FF_LOCALIP_TYPE_IPV4_BIT) {
|
||||
ffStrbufAppendS(&defaultRoute, "ipv4");
|
||||
}
|
||||
if (ip->defaultRoute & FF_LOCALIP_TYPE_IPV6_BIT) {
|
||||
if (defaultRoute.length > 0) {
|
||||
ffStrbufAppendC(&defaultRoute, ',');
|
||||
}
|
||||
ffStrbufAppendS(&defaultRoute, "ipv6");
|
||||
}
|
||||
FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) {
|
||||
FF_ARG(ip->ipv4, "ipv4"),
|
||||
FF_ARG(ip->ipv6, "ipv6"),
|
||||
FF_ARG(ip->mac, "mac"),
|
||||
FF_ARG(ip->name, "ifname"),
|
||||
FF_ARG(ip->defaultRoute, "is-default-route"),
|
||||
FF_ARG(defaultRoute, "is-default-route"),
|
||||
FF_ARG(ip->mtu, "mtu"),
|
||||
FF_ARG(buffer, "speed"),
|
||||
FF_ARG(ip->flags, "flags"),
|
||||
@@ -151,6 +164,7 @@ bool ffPrintLocalIp(FFLocalIpOptions* options) {
|
||||
}
|
||||
++index;
|
||||
ffStrbufClear(&buffer);
|
||||
ffStrbufClear(&defaultRoute);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,5 +476,5 @@ FFModuleBaseInfo ffLocalIPModuleInfo = {
|
||||
{ "Link speed (formatted)", "speed" },
|
||||
{ "Interface flags", "flags" },
|
||||
})),
|
||||
.defaultOrder = 49,
|
||||
.defaultOrder = 50,
|
||||
};
|
||||
|
||||
@@ -57,7 +57,9 @@ bool ffGenerateLogoJsonResult([[maybe_unused]] FFLogoOptions* options, yyjson_mu
|
||||
yyjson_mut_obj_add_str(doc, obj, "colorTitle", logo->colorTitle);
|
||||
|
||||
yyjson_mut_val* typeArr = yyjson_mut_obj_add_arr(doc, obj, "type");
|
||||
if (logo->type & FF_LOGO_LINE_TYPE_NORMAL) {
|
||||
// `FF_LOGO_LINE_TYPE_NORMAL` is 0, so a bit test here is always false and "normal" would be dead
|
||||
// code. `src/logo/logo.c` tests the same flag with `!=`, which is the intended semantics.
|
||||
if (logo->type == FF_LOGO_LINE_TYPE_NORMAL) {
|
||||
yyjson_mut_arr_add_str(doc, typeArr, "normal");
|
||||
}
|
||||
if (logo->type & FF_LOGO_LINE_TYPE_SMALL_BIT) {
|
||||
|
||||
@@ -296,5 +296,5 @@ FFModuleBaseInfo ffMediaModuleInfo = {
|
||||
{ "Player ID", "player-id" },
|
||||
{ "URL", "url" },
|
||||
})),
|
||||
.defaultOrder = 47,
|
||||
.defaultOrder = 48,
|
||||
};
|
||||
|
||||
@@ -154,5 +154,5 @@ FFModuleBaseInfo ffMemoryModuleInfo = {
|
||||
{ "Percentage used (num)", "percentage" },
|
||||
{ "Percentage used (bar)", "percentage-bar" },
|
||||
})),
|
||||
.defaultOrder = 38,
|
||||
.defaultOrder = 39,
|
||||
};
|
||||
|
||||
@@ -186,5 +186,5 @@ FFModuleBaseInfo ffMouseModuleInfo = {
|
||||
{ "Mouse name", "name" },
|
||||
{ "Mouse serial number", "serial" },
|
||||
})),
|
||||
.defaultOrder = 63,
|
||||
.defaultOrder = 64,
|
||||
};
|
||||
|
||||
@@ -126,7 +126,10 @@ void ffParseNetIOJsonObject(FFNetIOOptions* options, yyjson_val* module) {
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "waitTime")) {
|
||||
options->waitTime = (uint32_t) yyjson_get_uint(val);
|
||||
if (!ffJsonConfigParseUInt32(val, &options->waitTime, UINT32_MAX)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(NetIO), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Property 'waitTime' must be a non-negative integer no greater than 4294967295");
|
||||
continue;
|
||||
}
|
||||
if (options->waitTime == 0) {
|
||||
options->waitTime = 1;
|
||||
}
|
||||
@@ -245,5 +248,5 @@ FFModuleBaseInfo ffNetIOModuleInfo = {
|
||||
{ "Number of packets dropped when receiving [per second]", "rx-drops" },
|
||||
{ "Number of packets dropped when sending [per second]", "tx-drops" },
|
||||
})),
|
||||
.defaultOrder = 66,
|
||||
.defaultOrder = 67,
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ bool ffGenerateOpenCLJsonResult([[maybe_unused]] FFOpenCLOptions* options, yyjso
|
||||
}
|
||||
|
||||
if (gpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) {
|
||||
yyjson_mut_obj_add_uint(doc, dedicatedMemory, "used", gpu->dedicated.total);
|
||||
yyjson_mut_obj_add_uint(doc, dedicatedMemory, "used", gpu->dedicated.used);
|
||||
} else {
|
||||
yyjson_mut_obj_add_null(doc, dedicatedMemory, "used");
|
||||
}
|
||||
@@ -155,5 +155,5 @@ FFModuleBaseInfo ffOpenCLModuleInfo = {
|
||||
{ "Platform name", "name" },
|
||||
{ "Platform vendor", "vendor" },
|
||||
})),
|
||||
.defaultOrder = 56,
|
||||
.defaultOrder = 57,
|
||||
};
|
||||
|
||||
@@ -164,5 +164,5 @@ FFModuleBaseInfo ffOpenGLModuleInfo = {
|
||||
{ "OpenGL shading language version", "slv" },
|
||||
{ "OpenGL library used", "library" },
|
||||
})),
|
||||
.defaultOrder = 55,
|
||||
.defaultOrder = 56,
|
||||
};
|
||||
|
||||
@@ -216,6 +216,7 @@ FFModuleBaseInfo ffOSModuleInfo = {
|
||||
{ "Version codename of the OS", "codename" },
|
||||
{ "Build ID of the OS", "build-id" },
|
||||
{ "Architecture of the OS", "arch" },
|
||||
{ "Release of the kernel", "kernel-release" },
|
||||
})),
|
||||
.defaultOrder = 3,
|
||||
};
|
||||
|
||||
@@ -306,5 +306,5 @@ FFModuleBaseInfo ffPhysicalDiskModuleInfo = {
|
||||
{ "Product revision", "revision" },
|
||||
{ "Device temperature (formatted)", "temperature" },
|
||||
})),
|
||||
.defaultOrder = 68,
|
||||
.defaultOrder = 69,
|
||||
};
|
||||
|
||||
@@ -201,5 +201,5 @@ FFModuleBaseInfo ffPhysicalMemoryModuleInfo = {
|
||||
{ "True if ECC enabled", "is-ecc-enabled" },
|
||||
{ "True if a memory module is installed in the slot", "is-installed" },
|
||||
})),
|
||||
.defaultOrder = 39,
|
||||
.defaultOrder = 40,
|
||||
};
|
||||
|
||||
@@ -143,5 +143,5 @@ FFModuleBaseInfo ffPlayerModuleInfo = {
|
||||
{ "Player Identifier", "id" },
|
||||
{ "URL name", "url" },
|
||||
})),
|
||||
.defaultOrder = 46,
|
||||
.defaultOrder = 47,
|
||||
};
|
||||
|
||||
@@ -142,9 +142,9 @@ FFModuleBaseInfo ffPowerAdapterModuleInfo = {
|
||||
{ "Power adapter watts", "watts" },
|
||||
{ "Power adapter name", "name" },
|
||||
{ "Power adapter manufacturer", "manufacturer" },
|
||||
{ "Power adapter model", "model" },
|
||||
{ "Power adapter model", "model-name" },
|
||||
{ "Power adapter description", "description" },
|
||||
{ "Power adapter serial number", "serial" },
|
||||
})),
|
||||
.defaultOrder = 45,
|
||||
.defaultOrder = 46,
|
||||
};
|
||||
|
||||
@@ -18,7 +18,10 @@ bool ffPrintProcesses(FFProcessesOptions* options) {
|
||||
|
||||
printf("%u (%u threads)\n", result.processes, result.threads);
|
||||
} else {
|
||||
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result.processes, "result") }));
|
||||
FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Processes), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) {
|
||||
FF_ARG(result.processes, "result"),
|
||||
FF_ARG(result.threads, "threads")
|
||||
}));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -105,6 +108,7 @@ FFModuleBaseInfo ffProcessesModuleInfo = {
|
||||
.generateJsonConfig = (void*) ffGenerateProcessesJsonConfig,
|
||||
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
|
||||
{ "Process count", "result" },
|
||||
{ "Thread count", "threads" },
|
||||
})),
|
||||
.defaultOrder = 13,
|
||||
};
|
||||
|
||||
@@ -142,5 +142,5 @@ FFModuleBaseInfo ffPublicIPModuleInfo = {
|
||||
{ "Public IP address", "ip" },
|
||||
{ "Location", "location" },
|
||||
})),
|
||||
.defaultOrder = 48,
|
||||
.defaultOrder = 49,
|
||||
};
|
||||
|
||||
@@ -90,7 +90,10 @@ void ffParseSeparatorJsonObject(FFSeparatorOptions* options, yyjson_val* module)
|
||||
}
|
||||
|
||||
if (unsafe_yyjson_equals_str(key, "times")) {
|
||||
options->times = (uint32_t) yyjson_get_uint(val);
|
||||
if (!ffJsonConfigParseUInt32(val, &options->times, UINT32_MAX)) {
|
||||
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Separator), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property 'times' must be a non-negative integer no greater than 4294967295");
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@ static void printDevice(FFSoundOptions* options, const FFSoundDevice* device, ui
|
||||
}
|
||||
|
||||
bool ffPrintSound(FFSoundOptions* options) {
|
||||
bool success = false;
|
||||
FF_LIST_AUTO_DESTROY result = ffListCreate();
|
||||
|
||||
const char* error = ffDetectSound(options, &result);
|
||||
@@ -93,7 +92,7 @@ bool ffPrintSound(FFSoundOptions* options) {
|
||||
ffStrbufDestroy(&device->platformApi);
|
||||
}
|
||||
|
||||
return success;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ffParseSoundJsonObject(FFSoundOptions* options, yyjson_val* module) {
|
||||
@@ -238,5 +237,5 @@ FFModuleBaseInfo ffSoundModuleInfo = {
|
||||
{ "Volume (in percentage bar)", "volume-percentage-bar" },
|
||||
{ "Platform API used", "platform-api" },
|
||||
})),
|
||||
.defaultOrder = 60,
|
||||
.defaultOrder = 61,
|
||||
};
|
||||
|
||||
@@ -220,5 +220,5 @@ FFModuleBaseInfo ffSwapModuleInfo = {
|
||||
{ "Percentage used (bar)", "percentage-bar" },
|
||||
{ "Name *", "name" },
|
||||
})),
|
||||
.defaultOrder = 40,
|
||||
.defaultOrder = 41,
|
||||
};
|
||||
|
||||
@@ -111,6 +111,7 @@ void ffParseTopJsonObject(FFTopOptions* options, yyjson_val* module) {
|
||||
{ "disk-read", FF_TOP_TYPE_DISK_READ },
|
||||
{ "disk-write", FF_TOP_TYPE_DISK_WRITE },
|
||||
{ "start-time", FF_TOP_TYPE_START_TIME },
|
||||
{ "threads", FF_TOP_TYPE_THREADS },
|
||||
{},
|
||||
});
|
||||
if (error) {
|
||||
@@ -305,5 +306,5 @@ FFModuleBaseInfo ffTopModuleInfo = {
|
||||
{ "Disk read formatted", "disk-read-formatted" },
|
||||
{ "Disk write formatted", "disk-write-formatted" },
|
||||
})),
|
||||
.defaultOrder = 36,
|
||||
.defaultOrder = 37,
|
||||
};
|
||||
|
||||
@@ -117,5 +117,5 @@ FFModuleBaseInfo ffTPMModuleInfo = {
|
||||
{ "TPM device version", "version" },
|
||||
{ "TPM general description", "description" },
|
||||
})),
|
||||
.defaultOrder = 69,
|
||||
.defaultOrder = 70,
|
||||
};
|
||||
|
||||
@@ -225,5 +225,5 @@ FFModuleBaseInfo ffUsersModuleInfo = {
|
||||
{ "Days of year after login", "days-of-year" },
|
||||
{ "Years fraction after login", "years-fraction" },
|
||||
})),
|
||||
.defaultOrder = 57,
|
||||
.defaultOrder = 58,
|
||||
};
|
||||
|
||||
@@ -136,5 +136,5 @@ FFModuleBaseInfo ffVersionModuleInfo = {
|
||||
{ "Compiler used when compiling", "compiler" },
|
||||
{ "Libc used when compiling", "libc" },
|
||||
})),
|
||||
.defaultOrder = 70,
|
||||
.defaultOrder = 71,
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ bool ffGenerateVulkanJsonResult([[maybe_unused]] FFVulkanOptions* options, yyjso
|
||||
yyjson_mut_val* obj = yyjson_mut_obj_add_obj(doc, module, "result");
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "apiVersion", &result->apiVersion);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "conformanceVersion", &result->conformanceVersion);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "instanceVersion", &result->instanceVersion);
|
||||
yyjson_mut_obj_add_strbuf(doc, obj, "driver", &result->driver);
|
||||
yyjson_mut_val* gpus = yyjson_mut_obj_add_arr(doc, obj, "gpus");
|
||||
FF_LIST_FOR_EACH (FFGPUResult, vulkanGpu, result->gpus) {
|
||||
@@ -96,7 +97,7 @@ bool ffGenerateVulkanJsonResult([[maybe_unused]] FFVulkanOptions* options, yyjso
|
||||
}
|
||||
|
||||
if (vulkanGpu->dedicated.used != FF_GPU_VMEM_SIZE_UNSET) {
|
||||
yyjson_mut_obj_add_uint(doc, dedicatedMemory, "used", vulkanGpu->dedicated.total);
|
||||
yyjson_mut_obj_add_uint(doc, dedicatedMemory, "used", vulkanGpu->dedicated.used);
|
||||
} else {
|
||||
yyjson_mut_obj_add_null(doc, dedicatedMemory, "used");
|
||||
}
|
||||
@@ -116,8 +117,6 @@ bool ffGenerateVulkanJsonResult([[maybe_unused]] FFVulkanOptions* options, yyjso
|
||||
yyjson_mut_obj_add_null(doc, sharedMemory, "used");
|
||||
}
|
||||
}
|
||||
|
||||
yyjson_mut_obj_add_uint(doc, gpuObj, "deviceId", vulkanGpu->deviceId);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -168,5 +167,5 @@ FFModuleBaseInfo ffVulkanModuleInfo = {
|
||||
{ "Conformance version", "conformance-version" },
|
||||
{ "Instance version", "instance-version" },
|
||||
})),
|
||||
.defaultOrder = 54,
|
||||
.defaultOrder = 55,
|
||||
};
|
||||
|
||||
@@ -125,5 +125,5 @@ FFModuleBaseInfo ffWeatherModuleInfo = {
|
||||
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
|
||||
{ "Weather result", "result" },
|
||||
})),
|
||||
.defaultOrder = 65,
|
||||
.defaultOrder = 66,
|
||||
};
|
||||
|
||||
@@ -261,5 +261,5 @@ FFModuleBaseInfo ffWifiModuleInfo = {
|
||||
{ "Connection channel width in MHz", "channel-width" },
|
||||
{ "Connection channel band in GHz", "band" },
|
||||
})),
|
||||
.defaultOrder = 51,
|
||||
.defaultOrder = 52,
|
||||
};
|
||||
|
||||
@@ -238,5 +238,5 @@ FFModuleBaseInfo ffZpoolModuleInfo = {
|
||||
{ "Fragmentation percentage bar", "frag-percentage-bar" },
|
||||
{ "Is read-only", "is-readonly" },
|
||||
})),
|
||||
.defaultOrder = 43,
|
||||
.defaultOrder = 44,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "fastfetch.h"
|
||||
#include "common/color.h"
|
||||
#include "common/io.h"
|
||||
#include "common/jsonconfig.h"
|
||||
#include "common/percent.h"
|
||||
#include "common/strutil.h"
|
||||
@@ -286,11 +287,11 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va
|
||||
yyjson_val* ndigits = yyjson_obj_get(val, "ndigits");
|
||||
if (ndigits) {
|
||||
if (!yyjson_is_uint(ndigits)) {
|
||||
return "display.temperature.ndigits must be an unsigned integer";
|
||||
return "display.temp.ndigits must be an unsigned integer";
|
||||
}
|
||||
uint64_t val = yyjson_get_uint(ndigits);
|
||||
if (val > 9) {
|
||||
return "display.temperature.ndigits must be between 0 and 9";
|
||||
return "display.temp.ndigits must be between 0 and 9";
|
||||
}
|
||||
options->tempNdigits = (uint8_t) val;
|
||||
}
|
||||
@@ -298,7 +299,7 @@ const char* ffOptionsParseDisplayJsonConfig(FFOptionsDisplay* options, yyjson_va
|
||||
yyjson_val* color = yyjson_obj_get(val, "color");
|
||||
if (color) {
|
||||
if (!yyjson_is_obj(color)) {
|
||||
return "display.temperature.color must be an object";
|
||||
return "display.temp.color must be an object";
|
||||
}
|
||||
|
||||
yyjson_val* green = yyjson_obj_get(color, "green");
|
||||
@@ -952,7 +953,7 @@ void ffOptionsInitDisplay(FFOptionsDisplay* options) {
|
||||
ffStrbufInitStatic(&options->keyValueSeparator, ": ");
|
||||
|
||||
options->showErrors = false;
|
||||
options->pipe = !isatty(STDOUT_FILENO) || !!getenv("NO_COLOR");
|
||||
options->pipe = !ffIsTerminal(STDOUT_FILENO) || !!getenv("NO_COLOR");
|
||||
options->disableLinewrap = false;
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
Reference in New Issue
Block a user