Compare commits

..

172 Commits

Author SHA1 Message Date
dependabot[bot] 61e3f6108c CI: Bump the github-actions group with 3 updates (#2569)
Bumps the github-actions group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [vmactions/solaris-vm](https://github.com/vmactions/solaris-vm).


Updates `github/codeql-action/init` from 4.37.8 to 4.37.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938)

Updates `github/codeql-action/analyze` from 4.37.8 to 4.37.9
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28...cdf488f595d80d6e07e03d4674febd5ab45fa938)

Updates `vmactions/solaris-vm` from 1.3.8 to 1.3.9
- [Release notes](https://github.com/vmactions/solaris-vm/releases)
- [Commits](https://github.com/vmactions/solaris-vm/compare/315163f088b66e55bbcc45928bd224d4973b2312...96d8d976f9e67d82ec6c7e8ce9c1060731f9e21c)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: vmactions/solaris-vm
  dependency-version: 1.3.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-12 23:38:17 +08:00
李通洲 eff8514536 CI (macOS): builds Intel version with macport 2026-09-12 00:08:16 +08:00
李通洲 f37a5a2543 OpenBSD: removes kvm dep 2026-09-11 23:38:10 +08:00
Carter Li 9a9f82b59e Processes (macOS): adds a comment 2026-09-11 10:40:44 +08:00
Carter Li 29818d2dca Top (macOS): improves performance and accuracy‌ of process name 2026-09-11 10:36:16 +08:00
Carter Li 222fe7e3f9 3rdparty (yyjson): upgrades to 0.13.0 2026-09-09 13:49:56 +08:00
Stian Halseth 7d0256fb85 CPU (Linux): adds SPARC support (#2572)
/proc/cpuinfo on sparc has none of the fields the generic fallback looks
for, so the module printed "Unknown (64)". The name is in "cpu", and the
frequency in "Cpu<n>ClkTck" -- in Hz, hex on sparc64, decimal on sparc32.
2026-09-08 20:31:55 +08:00
Carter Li a70c37fdaf Logo (Builtin): removes Zerene
Distro discontinued #2567
2026-09-07 10:58:46 +08:00
Sultaniiazov David 66e9e38f28 Logo (Builtin): adds ALT Atomic (#2549)
Co-authored-by: Vladimir Romanov <rirusha@altlinux.org>
2026-09-06 23:29:42 +08:00
李通洲 5d290cc3d5 Common (CommandOption): find module configuration from JSON config file 2026-09-05 20:14:58 +08:00
Guiorgy 2e542bea64 CMake: bumps min version to 3.21 (#2565)
C_STANDARD C17 and C23 were only introduced in CMake 3.21
2026-09-05 13:43:05 +08:00
李通洲 c708b6f5cd CPU (Linux): adds apple code of M5x 2026-09-05 00:32:58 +08:00
李通洲 6a275da8b1 Sound: removes an unused file 2026-09-05 00:32:31 +08:00
李通洲 ecee5694fb Global: uses strtoull for uint64_t
Fixes #2563 #2564
2026-09-04 19:35:26 +08:00
Matt Housh 4e78f1b703 Packages (Linux): adds support for package detection for CRUX linux 2026-09-04 18:14:21 +08:00
李通洲 62f78ca60c Processes (Windows): uses SystemHandleCountInformation if available 2026-09-03 15:27:40 +08:00
李通洲 00af2d1df2 WM: uses SystemBasicProcessInformation if available 2026-09-03 10:04:55 +08:00
李通洲 1128a75d8c InitSystem: uses SystemBasicProcessInformation if available 2026-09-03 10:04:55 +08:00
Carter Li ce61783c4b Wallpaper (macOS): extracts image path from NSData Configuration field
Also moves NSWorkspace method to the last method to try

Fixes #2559
2026-09-03 09:57:08 +08:00
Piotr Kubaj 20597bff00 Logo (image): also try unsuffixed ImageMagick library names
FreeBSD (and other systems that build ImageMagick without encoding the
quantum depth into the library name) install libMagickCore-7.so and
libMagickCore-6.so without a .Q16HDRI / .Q16 suffix.  dlopen currently
only tries the suffixed names, so image logos always fall back to ASCII
art with "Image Magick library not found" even when ImageMagick is
installed.  Append the plain library names as a last resort.
2026-09-02 17:06:16 +08:00
Carter Li caf64f5e4d Packaging: updates debian stuff [ci skip] 2026-09-02 10:52:57 +08:00
Carter Li 90a6e90b19 Processes (macOS): eliminates a compiler warning 2026-09-02 10:45:52 +08:00
Carter Li e9f719f37a Doc: update CONTRIBUTING.md [ci skip] 2026-09-02 10:44:47 +08:00
Carter Li 8c98bf1953 CMake: discovers module file automatically 2026-09-02 10:44:47 +08:00
Carter Li 1c1136ebd1 Merge pull request #2555 from fastfetch-cli/dev
Release: v2.68.1
2026-09-01 19:44:59 +08:00
李通洲 2787d97973 Release: v2.68.1 2026-09-01 19:21:56 +08:00
李通洲 2e06a73102 Modules: exposes localized module name in custom key format as {module-name} 2026-09-01 19:18:02 +08:00
Carter Li 37c198d997 Revert "CMAKE: bumps minimum CMake version to 3.21.0 (#2517)" (#2554)
I don't know why bumping minimal CMake version changes the behavior of cpack, but it does.

Fixes #2553

This reverts commit 233c481f7a.
2026-09-01 18:58:08 +08:00
Carter Li 37afe5a79d Packaging: updates debian stuff [ci skip] 2026-09-01 16:35:30 +08:00
Carter Li 0ebf09754b Merge pull request #2551 from fastfetch-cli/dev
Release: v2.68.0
2026-09-01 16:20:15 +08:00
Carter Li 0b1bc5b527 CI: upgrades deps; reenables Haiku 2026-09-01 14:14:14 +08:00
Carter Li 3d107764fa Wifi (Linux): fixes compatibility with old kernel headers 2026-09-01 14:14:14 +08:00
李通洲 4863a173e4 Wifi (linux): enables netlink on big-endian platforms 2026-09-01 00:34:55 +08:00
李通洲 8ea4468eec Common (Endian): adds new header 2026-09-01 00:20:40 +08:00
李通洲 b0ba084b5e Global: address review comments v2 2026-08-31 22:03:06 +08:00
李通洲 43d01f26ea Top: handle unknown process names gracefully in output 2026-08-31 20:38:20 +08:00
李通洲 b5ffc32b80 Top / Processes (Windows): improves detection of system / kernel processes detection 2026-08-31 20:37:31 +08:00
李通洲 4ff931c06f Top: adds thread count detection 2026-08-31 20:37:31 +08:00
李通洲 6eef800ea2 Top: supports sorting by start-time 2026-08-31 19:47:14 +08:00
李通洲 b9e4d04d65 Global: addresses review comments generated by CI 2026-08-31 19:10:27 +08:00
Carter Li 9c791b2c12 Doc: adds CONTRIBUTING.md 2026-08-31 15:31:45 +08:00
Carter Li 76b6768141 JsonSchema: regenerates moduleFormat 2026-08-31 15:31:45 +08:00
Carter Li 42002a6240 CPU (Linux): supports more Snapdragon X SoCs 2026-08-31 15:31:45 +08:00
Carter Li ef7f5aeac3 CPU (Android): adds pretty names of new SoCs 2026-08-31 14:26:16 +08:00
Carter Li d5214af189 Global: fixes British spellings and typos found by AI 2026-08-31 13:44:19 +08:00
Carter Li a33d6326f1 CMake: enables CONFIGURE_DEPENDS to FF_MODULE_DIRS globbing 2026-08-31 13:31:21 +08:00
Carter Li df99241424 L10n (gl): adds missing missing Galician translations 2026-08-31 13:11:00 +08:00
dependabot[bot] 5b1ad1008d CI: Bump the github-actions group with 3 updates
Bumps the github-actions group with 3 updates: [uraimo/run-on-arch-action](https://github.com/uraimo/run-on-arch-action), [github/codeql-action/init](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `uraimo/run-on-arch-action` from 3.1.0 to 3.2.0
- [Release notes](https://github.com/uraimo/run-on-arch-action/releases)
- [Commits](https://github.com/uraimo/run-on-arch-action/compare/f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9...460cb8e6d9f726a588fc9b5e681c8a6cab09ae41)

Updates `github/codeql-action/init` from 4.37.7 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28)

Updates `github/codeql-action/analyze` from 4.37.7 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28)

---
updated-dependencies:
- dependency-name: uraimo/run-on-arch-action
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 08:41:25 +08:00
李通洲 39024abae9 Processes (Haiku): honors options->countKprocs 2026-08-30 19:54:58 +08:00
Carter Li 4dba32aabb WM (macOS): improves reliability of WM plugin detection 2026-08-30 19:04:09 +08:00
李通洲 753ddec3c5 Memory (Windows): prefers NQSI 2026-08-29 20:09:48 +08:00
Carter Li b04587e0cb Presets: moves top to the bottom of running modules [ci skip] 2026-08-29 13:03:49 +08:00
Carter Li 3abcf4195f Processes: corrects the structure of JSON result 2026-08-29 12:14:09 +08:00
李通洲 af8a42270c Top (Hurd): reports startTime; uses time_value64_t 2026-08-29 12:09:40 +08:00
李通洲 1c9e521775 Top: disables FF_TOP_TYPE_DISK on Hurd and Haiku 2026-08-29 12:09:31 +08:00
Carter Li 7fe08bddd0 Processes: adds option countKprocs 2026-08-29 00:58:15 +08:00
Carter Li 41da49f579 Top (SunOS / Hurd): skips kernel tasks 2026-08-29 00:40:08 +08:00
Carter Li f169165599 Top (Linux): improves performance of stat parsing 2026-08-28 18:45:30 +08:00
Carter Li 2c4caf8e0d Top: removes realloc retries 2026-08-28 16:22:33 +08:00
Carter Li 51b370a105 Processes: adds total thread count detection 2026-08-28 15:46:23 +08:00
Carter Li 14cf6bfdc1 Top (macOS): adds comments 2026-08-28 15:46:23 +08:00
Carter Li 4d00dc6802 Logo (Builtin): adds omarchy 2026-08-28 09:49:48 +08:00
Carter Li d7de7c41ca CI: disables fail on alert 2026-08-27 15:28:18 +08:00
Carter Li d80fe3f004 Logo (Builtin): adds macOS_old 2026-08-27 14:58:32 +08:00
Carter Li dcce4ccdb0 CI: moves benchmark-index to /.github as required by the script 2026-08-27 14:57:22 +08:00
Carter Li 191c5f2ef5 Doc: updates README 2026-08-27 14:29:17 +08:00
Carter Li 1dc794fac0 Doc: updates changelog [ci skip] 2026-08-27 14:23:16 +08:00
Carter Li 5936c1cef4 CI: adds benchmark job 2026-08-27 14:23:16 +08:00
Carter Li 04f26a29eb CI: enables bluetooth module on platforms other than macOS 2026-08-27 09:45:58 +08:00
Carter Li c9ad429a1b Global: introduces global macro FF_PATH_PKG_BASE to replace _PATH_LOCALBASE 2026-08-26 23:12:25 +08:00
李通洲 2b42db58da LM (OpenBSD): adds support 2026-08-26 17:43:25 +08:00
Carter Li 32757b0e43 LM (Windows): adds support 2026-08-26 17:43:25 +08:00
Carter Li c5598d2361 LM (macOS): adds support 2026-08-26 17:43:25 +08:00
Carter Li 203cd7cbcf LM (Linux): adds pretty name 2026-08-26 17:43:25 +08:00
Carter Li fb309134b5 LM (Linux): queries systemd session info via dbus 2026-08-26 17:43:25 +08:00
Carter Li 8b162e89de WMDE: improves performance 2026-08-26 14:14:24 +08:00
Carter Li 9670b585fb LM: detects LM from processes list if systemd is not available
2. removes type field
3. small optimizations
2026-08-26 14:12:08 +08:00
Carter Li 3b76ade662 Shell: fixes the fast path of ash version detection 2026-08-26 08:50:08 +08:00
李通洲 1b1df2e731 Common (Settings): tidy 2026-08-25 23:27:36 +08:00
李通洲 6c19a8b4ec Top (DragonFly): fixes building 2026-08-25 23:27:24 +08:00
李通洲 71728247ab Common (Sysctl): handle varying data lengths in integer retrieval functions 2026-08-25 23:24:31 +08:00
李通洲 4344aa2ff1 Top: adds option showType: <cpu|mem|disk> 2026-08-25 20:48:34 +08:00
李通洲 b7d8c12015 CI: adds top module 2026-08-25 18:24:32 +08:00
Carter Li 63d16ca854 Top: removes option kernelTask (never implemented) 2026-08-25 16:30:04 +08:00
Carter Li f15a1e96ce Top (Linux): don't error out when reading /proc/pid/io fails 2026-08-25 16:27:53 +08:00
Carter Li 55bfcd6d86 Top (Android): fixes building 2026-08-25 13:35:32 +08:00
Carter Li 880284fa9c DisplayServer (Android): enables Wayland support
Fixes #2530
2026-08-25 13:32:28 +08:00
Carter Li 143939f19b DE (Android): adds asus zenfone support
Fixes #2541
2026-08-25 09:08:35 +08:00
dependabot[bot] a979d8ae81 CI: Bump the github-actions group with 4 updates
Bumps the github-actions group with 4 updates: [cross-platform-actions/action](https://github.com/cross-platform-actions/action), [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request).


Updates `cross-platform-actions/action` from 1.3.0 to 1.4.0
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/5ea7e8e4677bd726033a10b094ba1c5762b15dee...24ef01df165c76df1ed2b9f9e9212e78dc2fc963)

Updates `github/codeql-action/init` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd)

Updates `github/codeql-action/analyze` from 4.37.6 to 4.37.7
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd)

Updates `signpath/github-action-submit-signing-request` from 2.2 to 2.3
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/b9d91eadd323de506c0c81cf0c7fe7438f3360fd...c92b958760219087e01f8d67a1669ed57afe2627)

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.37.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 11:31:38 -05:00
Carter Li b5897f784f Processes (GNU): uses native impl 2026-08-25 00:05:49 +08:00
Carter Li 83cc7a94a4 Top: adds new module 2026-08-25 00:05:49 +08:00
李通洲 5edda068f2 PhysicalDisk (DragonFly): eliminates‌ compiler warnings 2026-08-24 20:58:50 +08:00
Carter Li 6d5cb520ed Logo (Builtin): adds artix2 in 2026-08-24 13:21:25 +08:00
Carter Li e7ecba4460 Doc: updates README [ci skip] 2026-08-21 10:40:50 +08:00
Carter Li e0c239729b L10n: adds cs id tr uk vi
Assisted-By: LLM
2026-08-21 10:19:06 +08:00
Samuel Torres 6570a7accc L10n (ES): fixes Spanish translations (#2537) 2026-08-21 08:31:42 +08:00
Samuel Torres 0c4ec62cac L10n: (gl) Added Galician Support 2026-08-21 00:25:32 +08:00
Carter Li 5d11b65d53 TerminalFont (Android): adds font size detection 2026-08-20 16:18:07 +08:00
Carter Li 8b40117c08 Wifi (OpenBSD): fortifies detection and adds channel to frequency helper 2026-08-20 16:04:00 +08:00
Carter Li b1f2bf3526 WM: uses full name in EN, as DE and LM modules do 2026-08-20 15:23:21 +08:00
Carter Li d8452f71ed Wifi (SunOS): fortifies code 2026-08-20 15:05:18 +08:00
Carter Li 2a269bfd4c Wifi: removes driver detection support 2026-08-19 23:28:23 +08:00
Carter Li cafe82b462 Battery (SunOS): adds basic detection impl 2026-08-19 13:24:05 +08:00
Carter Li db8ee8159d Wifi (Haiku): adds basic Wifi detection impl 2026-08-19 11:05:35 +08:00
Carter Li f0765b1a13 Wifi (SunOS): adds basic Wifi detection impl 2026-08-19 10:43:34 +08:00
Carter Li 93566cb73c Wifi (Android / OpenBSD): inits unsupported properties 2026-08-19 10:16:30 +08:00
Carter Li 6d0d093d62 Wifi (NetBSD): adds channel width detection 2026-08-19 10:15:30 +08:00
Carter Li 54fe4ffece Wifi (FreeBSD): adds channel width detection 2026-08-19 09:40:23 +08:00
Carter Li 124bbb06f9 Wifi (macOS): fixes compatibility for old Intel MBP 2026-08-19 09:17:12 +08:00
Carter Li f04a53337d Wifi (macOS): adds channel width, accurate channel frequency and device driver detection 2026-08-18 19:32:01 +08:00
李通洲 514efab8eb Wifi (Windows): adds channel width and device driver detection 2026-08-18 19:32:01 +08:00
Carter Li 232cd6659b Wifi (Linux): adds channel width and device driver detection 2026-08-18 19:32:01 +08:00
Carter Li 7c93f9c82a GPU (macOS): removes useless @available tests 2026-08-18 19:32:01 +08:00
Wehrwolfmann e5b6e3612a L10n: falls back to English when the auto-detected locale is unsupported
--key-language with an empty value (and "language": null in the config)
asks for the system locale, not for a specific language. When that locale
has no translation, fastfetch aborted with exit 477 instead of printing
anything, so the same config broke on every locale outside the twelve
supported ones, and under LC_ALL=C variants such as POSIX.

Auto-detected locales now fall back to English. An explicitly requested
language still errors out, so typos and zh are still reported.
2026-08-17 20:14:39 -05:00
Carter Li 6cb15885c7 L10n: don't use ffStrbufCreateStatic for buffers that will be modified later 2026-08-18 09:11:50 +08:00
Carter Li e6d90c2081 L10n (Zh): uses a different name for Monitor 2026-08-17 14:20:46 +08:00
Carter Li 679d868b4a L10n (Zh): clarifies Disk actually detects Disk partitions 2026-08-17 13:57:23 +08:00
Carter Li 72d240f325 L10n (Zh): clarifies Bluetooth actually detects Bluetooth devices 2026-08-17 13:42:30 +08:00
Carter Li 2ca7e037ef L10n (Zh): translates shell 2026-08-17 13:36:09 +08:00
Carter Li 5e9c31ac31 Completion (Fish): prints enum description [ci skip] 2026-08-17 10:42:24 +08:00
Wehrwolfmann bcb48774a0 Modules: proofreads Russian l10n strings 2026-08-16 12:01:42 -05:00
Wehrwolfmann 9ff7969acf Bluetooth / BluetoothRadio: keeps the brand name in Latin script in Russian keys 2026-08-16 12:01:42 -05:00
李通洲 7fbd06a135 Doc: updates README.md [ci skip] 2026-08-16 22:16:56 +08:00
Hashing bbb906ba10 L10n (Pt): matches all Portuguese locales, not only bare pt (#2521)
359cb0a79a renamed the key from `pt_BR` to
`pt` but kept the exact-match guard, so only the literal string `pt`
matched. Every real Portuguese locale (`pt_BR`, `pt_PT` and their charset
and modifier variants) fell through to `goto error` and aborted.

Return the offset unconditionally, matching every other two-letter case.
2026-08-16 02:15:39 -05:00
李通洲 2de674ec9a L10n (Ar / He): adds support
Fixes #2520
2026-08-16 10:52:23 +08:00
李通洲 b700d9ded5 L10n (Ru): changes Блютус to Блютуз
Ref: https://github.com/fastfetch-cli/fastfetch/issues/2503#issuecomment-5304614812
2026-08-16 10:52:23 +08:00
李通洲 359cb0a79a L10n (Pt): renames pt_BR to pt 2026-08-16 10:52:14 +08:00
李通洲 45f9de6cd2 L10n: improves performance 2026-08-16 10:36:33 +08:00
Guiorgy 233c481f7a CMAKE: bumps minimum CMake version to 3.21.0 (#2517)
C_STANDARD 23 was added in 3.21
2026-08-15 09:15:03 +08:00
Carter Li b677f1e902 Theme: improves GTK theme/icons/font detection on standalone WMs (Hyprland, sway, niri, i3)
Fixes #2504
2026-08-15 09:08:29 +08:00
Carter Li 8d82eedaef DiskIO / NetIO: updates default value of waitTime to reduce latency
Also corrects docs.
2026-08-15 08:53:00 +08:00
Carter Li 4dc27b365f DiskIO / NetIO: fixes I/O rate calculation precision, prevent div-by-zero 2026-08-15 08:51:45 +08:00
李通洲 e5c3fa4023 CI: fixes compiling errors 2026-08-15 00:00:27 +08:00
李通洲 bdba78c45c CI: silences codespell errors 2026-08-14 23:56:29 +08:00
Carter Li f95167e907 Global: adds option key-language
Fixes #2503
2026-08-14 23:33:54 +08:00
Carter Li 3908c39ce2 Modules: adds l10n strings 2026-08-14 22:47:24 +08:00
李通洲 858d3749f4 FFstrbuf: adds FF_STRBUF_STATIC 2026-08-14 18:27:53 +08:00
李通洲 51f36fa4b4 FFstrbuf: adds comments [ci skip] 2026-08-14 17:54:36 +08:00
李通洲 bfe30eec79 Doc: updates changelog [ci skip] 2026-08-14 17:05:06 +08:00
Carter Li 4dd2f2d34b Merge pull request #2516 from fastfetch-cli/dev 2026-08-14 01:20:28 -05:00
Carter Li 60431c0321 Doc: update changelog [ci skip] 2026-08-14 13:50:22 +08:00
李通洲 7b2b3405ae Locale (Windows): reports the code page used by the console 2026-08-14 13:38:14 +08:00
Carter Li 965ed4ba79 Library (Windows): don't assume DataTableEntry->FullDllName is null-terminated 2026-08-14 09:41:30 +08:00
李通洲 00603357ea Processing (Windows): improves performance of environement string creation 2026-08-14 09:41:30 +08:00
OctoBored 5ea299a8d2 Doc: restores Star History in README.md
The Star History chart was removed in 8df0a1c9 (Fixes #2508) because the previous chart was broken due to GitHub stargazer API restrictions. This restores it at the same location using a working data source that requires no API token, so the chart renders correctly again.
2026-08-13 05:43:43 -05:00
Carter Li 368a4c5a73 Release v2.67.1 2026-08-13 11:02:18 +08:00
Carter Li 8df0a1c97e Doc: removes Star History in README.md
Fixes #2508
2026-08-13 10:44:35 +08:00
Carter Li 7098cd86cb Display: fixes bright colours incorrectly active in light theme
Fixes #2509
2026-08-13 10:44:35 +08:00
njk 681749ed54 TerminalFont (Ghostty): fixes font-family fallback order (#2510) 2026-08-12 20:04:19 -05:00
李通洲 c4695b8cf1 Processing (Windows): sets LANG=C when spawning child processes 2026-08-12 13:54:32 +08:00
李通洲 1d12003555 Locale (Windows): uses $LANG if available e.g. MSYS2 2026-08-12 10:19:22 +08:00
Carter Li 16b10c162b Revert "Locale (macOS): queries system-wide locale preference"
This reverts commit 8b8efcdcce.
2026-08-12 10:07:34 +08:00
Carter Li 51cfd4b5ed OS (Android): optimises code 2026-08-12 09:52:31 +08:00
JunaidQrysh a0452b8323 Logo (Image): don't block images support in zellij 2026-08-11 17:41:45 +08:00
Carter Li ebf38e829d OS (Android): reports real code names 2026-08-11 11:02:20 +08:00
HashimAbdulaziz 4c2af4071c TerminalFont (Alacritty): fix font size never being parsed from config
The query array gained a third entry in
dd30c82b83 but numQueries stayed at 2, so
`size =` was never queried and every Alacritty user silently got the
hardcoded 11.25 fallback.
2026-08-09 02:23:17 -05:00
李通洲 e3b8e94055 Wallpaper (macOS): uses the official API if possible 2026-08-09 11:33:58 +08:00
李通洲 1d4b0236ac WMTheme (macOS): migrates impl to pure C using CoreFoundation 2026-08-09 10:25:13 +08:00
李通洲 da42108732 Cursor (macOS): migrates impl to pure C using CoreFoundation 2026-08-09 10:22:21 +08:00
李通洲 8b8efcdcce Locale (macOS): queries system-wide locale preference 2026-08-09 10:12:37 +08:00
李通洲 8bc4464b64 Library: makes ffLibraryIterateDynamicLibs return false on failure 2026-08-09 10:11:25 +08:00
Carter Li 4361c5e787 Library: adds function ffLibraryIterateDynamicLibs 2026-08-09 01:07:19 +08:00
李通洲 558a90d4e0 Loadavg (Haiku): adds support on R1B6 2026-08-09 01:07:19 +08:00
Carter Li 881730d0bd TerminalFont: improves Ghostty config loading with fallback support
Fixes: #2500
2026-08-08 22:40:34 +08:00
Carter Li ac8fc63c28 Doc: updates changelog [ci skip] 2026-08-07 16:39:23 +08:00
Yelninei 8686820c1a CMake (Hurd): Define O_PATH to O_NORW.
This is a noop because O_RW is 0 but O_NORW is the correct constant to
open a file without read/write permissions.
2026-08-07 02:23:29 -05:00
Carter Li 39b3dc001a CMake: adds C23 feature detection and compatibility workarounds for Apple clang 17 2026-08-07 11:00:12 +08:00
Carter Li ba60d7975c Camera (macOS): simplifies code 2026-08-07 09:37:39 +08:00
Carter Li 51a4f34bb5 Codec (macOS): fixes a symbol not found error when running on 10.15 2026-08-07 09:37:39 +08:00
Carter Li 01bb73b9e8 Packaging: updates debian stuff [ci skip] 2026-08-06 16:40:38 +08:00
Carter Li 56da8f8110 Merge pull request #2497 from fastfetch-cli/dev 2026-08-06 03:07:26 -05:00
dependabot[bot] 6d6302e327 CI: Bump the github-actions group with 2 updates
Bumps the github-actions group with 2 updates: [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) and [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request).


Updates `docker/setup-qemu-action` from 4.1.0 to 4.2.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/06116385d9baf250c9f4dcb4858b16962ea869c3...96fe6ef7f33517b61c61be40b68a1882f3264fb8)

Updates `signpath/github-action-submit-signing-request` from 1.3 to 2.2
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/ced31329c0317e779dad2eec2a7c3bb46ea1343e...b9d91eadd323de506c0c81cf0c7fe7438f3360fd)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.2'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-05 21:28:37 -05:00
314 changed files with 10266 additions and 2700 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
check-filenames =
builtin = clear,rare,usage,informal
skip = */.git,*/cmake-build-*,*/.idea,*/completions,*/presets,*/screenshots,*/tests,*/3rdparty,*/logo/ascii,./src/detection/gpu/asahi_drm.h,./src/detection/cpu/cpu_x86.c,./src/detection/gpu/gpu_bsddrm.c,./src/detection/displayserver/linux/wayland/*-protocol.*,./build,./build-*,./build_*,./src/common/impl/genconfig.c
ignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu,renderD,SME,Carmel,kinda
ignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu,renderD,SME,Carmel,kinda,symbole,connexion,comando,processus,lokale,adres,titel,som,environnement,Sistem,Lokal,Lokal,Sistem,Paket,Hava
+405
View File
@@ -0,0 +1,405 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fastfetch · Benchmark</title>
<style>
:root {
color-scheme: light;
--page: #f5f7fb;
--card: #fff;
--text: #172033;
--muted: #64748b;
--border: #dbe3ef;
--accent: #2563eb;
--accent-soft: #dbeafe;
--chart-text: #334155;
--chart-grid: #cbd5e1;
}
:root[data-theme="dark"] {
color-scheme: dark;
--page: #0f172a; --card: #172033; --text: #e5edf8; --muted: #9aabc1; --border: #334155; --accent-soft: #1e3a8a;
--chart-text: #f1f5f9;
--chart-grid: #475569;
}
* { box-sizing: border-box; }
body { max-width: 1440px; margin: 0 auto; padding: 2.5rem 1.25rem 4rem; background: var(--page); color: var(--text); font: 16px/1.5 system-ui, -apple-system, sans-serif; }
header { margin-bottom: 1.75rem; }
.header-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
h1 { margin: 0; font-size: clamp(2rem, 4vw, 3rem); letter-spacing: -.04em; }
h2 { margin: 0; font-size: 1.25rem; }
p { color: var(--muted); }
.intro { margin: .6rem 0 1rem; }
.links a { color: var(--accent); font-weight: 600; text-decoration: none; }
.links a:hover { text-decoration: underline; }
.theme-toggle, .fullscreen { border: 1px solid var(--border); border-radius: .55rem; padding: .5rem .8rem; background: var(--card); color: var(--text); cursor: pointer; font: inherit; font-weight: 600; }
.theme-toggle:hover, .fullscreen:hover { border-color: var(--accent); color: var(--accent); }
.tabs { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1.5rem 0; padding: .35rem; border: 1px solid var(--border); border-radius: .8rem; background: var(--card); }
.tab { border: 0; border-radius: .55rem; padding: .65rem 1.15rem; background: transparent; color: var(--muted); cursor: pointer; font: inherit; font-weight: 700; }
.tab:hover, .tab[aria-selected="true"] { background: var(--accent); color: #fff; }
.tab-panel[hidden] { display: none; }
.platform-title { display: flex; align-items: center; gap: .65rem; margin: 1.5rem 0 1rem; }
.platform-title::before { content: ''; width: .35rem; height: 1.4rem; border-radius: 1rem; background: var(--accent); }
.charts { display: flex; flex-direction: column; gap: 1.25rem; }
.chart { min-width: 0; padding: 1.25rem; border: 1px solid var(--border); border-radius: 1rem; background: var(--card); box-shadow: 0 8px 28px #0f172a0c; }
.chart:fullscreen { overflow: auto; padding: 2rem; background: var(--page); }
.chart:fullscreen .canvas-wrap { height: calc(100vh - 13rem); }
.chart-header { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: .75rem; }
.chart-header span { color: var(--muted); font-size: .85rem; }
.canvas-wrap { position: relative; height: 360px; }
.chart-actions { display: flex; align-items: center; gap: .75rem; }
.legend-actions { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1rem; }
.legend-action { border: 1px solid var(--border); border-radius: .45rem; padding: .35rem .6rem; background: var(--card); color: var(--muted); cursor: pointer; font: inherit; font-size: .8rem; }
.legend-action:hover { border-color: var(--accent); color: var(--accent); }
.legend { display: flex; flex-wrap: wrap; gap: .35rem .9rem; margin-top: 1rem; }
.legend-item { display: inline-flex; align-items: center; gap: .4rem; border: 0; padding: .15rem 0; background: transparent; color: var(--text); cursor: pointer; font: inherit; font-size: .85rem; }
.legend-item.is-hidden { color: var(--muted); }
.legend-swatch { width: .75rem; height: .75rem; border-radius: 50%; }
.legend-item.is-hidden .legend-swatch { opacity: .35; }
.chart-tooltip { position: fixed; z-index: 1000; pointer-events: none; width: min(90vw, 900px); max-height: calc(100vh - 24px); overflow: hidden; padding: .7rem .85rem; border: 1px solid var(--border); border-radius: .6rem; background: var(--card); color: var(--text); box-shadow: 0 10px 30px #0f172a33; font-size: .8rem; opacity: 0; transition: opacity .1s ease; }
.chart-tooltip-title { margin-bottom: .35rem; font-weight: 700; white-space: normal; overflow-wrap: anywhere; }
.chart-tooltip-body { max-height: calc(100vh - 76px); overflow: auto; columns: 220px auto; column-gap: 1.5rem; }
.chart-tooltip-row { display: flex; align-items: center; gap: .45rem; break-inside: avoid; white-space: nowrap; }
.chart-tooltip-swatch { width: .55rem; height: .55rem; border-radius: 50%; flex: 0 0 auto; }
.empty { padding: 2rem 0; color: var(--muted); }
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js/dist/chart.umd.min.js"></script>
<script src="data.js"></script>
</head>
<body>
<header>
<div class="header-row">
<h1>Fastfetch Benchmark</h1>
<button id="theme-toggle" class="theme-toggle" type="button"></button>
</div>
<p class="intro">Performance history grouped by CPU family. Each chart contains all modules and total process time for one operating system and architecture. Module curves are available from the legend and hidden by default.</p>
<p class="links"><a href="data.js">Download raw benchmark data</a></p>
</header>
<nav id="tabs" class="tabs" aria-label="CPU architecture"></nav>
<main id="panels"></main>
<script>
(() => {
const benchmarkData = window.BENCHMARK_DATA || {};
const entries = Object.values(benchmarkData.entries || {}).flat();
const groups = new Map();
function architectureFamily(arch) {
const normalized = arch.toLowerCase();
if (/^(amd64|x86_64|x86|i[3-6]86)$/.test(normalized)) return 'Intel';
if (/^(aarch64|arm64|armv[5-8].*|arm)$/.test(normalized)) return 'ARM';
return 'Other';
}
function parseName(name) {
let match = name.match(/^fastfetch (linux|macos|windows) ([^ ]+) (module .+|total process time)$/i);
if (match) {
return { platform: match[1].toLowerCase(), arch: match[2], metric: match[3] };
}
// Keep older macOS records visible while new records use the platform field.
match = name.match(/^fastfetch ([^ ]+) (module .+|total process time)$/i);
return match ? { platform: 'macos', arch: match[1], metric: match[2] } : null;
}
function entryMetadata(entry) {
const raw = entry.commit && entry.commit.timestamp || entry.date;
const date = new Date(raw);
return {
date: Number.isNaN(date.valueOf()) ? String(entry.date || '') : date.toISOString(),
sha: entry.commit && entry.commit.id ? entry.commit.id : 'unknown'
};
}
for (const entry of entries) {
const metricsByGroup = new Map();
for (const bench of entry.benches || []) {
const parsed = parseName(bench.name);
if (!parsed) continue;
const key = `${parsed.platform} ${parsed.arch}`;
if (!metricsByGroup.has(key)) metricsByGroup.set(key, new Map());
metricsByGroup.get(key).set(parsed.metric, bench.value);
}
for (const [key, metrics] of metricsByGroup) {
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push({ ...entryMetadata(entry), metrics });
}
}
const colors = [
'#2563eb', '#dc2626', '#16a34a', '#9333ea', '#ea580c', '#0891b2',
'#db2777', '#65a30d', '#4f46e5', '#ca8a04', '#0f766e', '#be123c'
];
const tabs = document.getElementById('tabs');
const panels = document.getElementById('panels');
const root = document.documentElement;
const themeToggle = document.getElementById('theme-toggle');
const savedTheme = localStorage.getItem('fastfetch-benchmark-theme');
let theme = savedTheme || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
const chartsByInstance = [];
function chartColors() {
const styles = getComputedStyle(root);
return {
text: styles.getPropertyValue('--chart-text').trim(),
grid: styles.getPropertyValue('--chart-grid').trim()
};
}
function externalTooltipHandler(context) {
const { chart, tooltip } = context;
let tooltipElement = chart.canvas.parentNode.querySelector('.chart-tooltip');
if (!tooltipElement) {
tooltipElement = document.createElement('div');
tooltipElement.className = 'chart-tooltip';
chart.canvas.parentNode.appendChild(tooltipElement);
}
if (tooltip.opacity === 0) {
tooltipElement.style.opacity = '0';
return;
}
tooltipElement.replaceChildren();
const title = document.createElement('div');
title.className = 'chart-tooltip-title';
const row = chart._benchmarkRows[tooltip.dataPoints[0].dataIndex];
title.textContent = `${new Date(row.date).toLocaleString()} · ${row.sha}`;
tooltipElement.appendChild(title);
const bodyElement = document.createElement('div');
bodyElement.className = 'chart-tooltip-body';
for (const body of [...tooltip.dataPoints].sort((a, b) => b.parsed.y - a.parsed.y)) {
const item = document.createElement('div');
item.className = 'chart-tooltip-row';
const swatch = document.createElement('span');
swatch.className = 'chart-tooltip-swatch';
swatch.style.background = body.dataset.borderColor;
const value = document.createElement('span');
value.textContent = `${body.dataset.label}: ${body.formattedValue} ms`;
item.append(swatch, value);
bodyElement.appendChild(item);
}
tooltipElement.appendChild(bodyElement);
const canvasRect = chart.canvas.getBoundingClientRect();
const margin = 12;
let left = canvasRect.left + tooltip.caretX + margin;
let top = canvasRect.top + tooltip.caretY + margin;
tooltipElement.style.left = `${left}px`;
tooltipElement.style.top = `${top}px`;
tooltipElement.style.opacity = '0';
const tooltipRect = tooltipElement.getBoundingClientRect();
if (left + tooltipRect.width > window.innerWidth - margin) left = canvasRect.left + tooltip.caretX - tooltipRect.width - margin;
if (top + tooltipRect.height > window.innerHeight - margin) top = canvasRect.top + tooltip.caretY - tooltipRect.height - margin;
tooltipElement.style.left = `${Math.max(margin, left)}px`;
tooltipElement.style.top = `${Math.max(margin, top)}px`;
tooltipElement.style.opacity = '1';
}
function updateTheme() {
root.dataset.theme = theme;
themeToggle.textContent = theme === 'dark' ? '☀ Light mode' : '☾ Dark mode';
themeToggle.setAttribute('aria-label', `Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`);
const colors = chartColors();
Chart.defaults.color = colors.text;
Chart.defaults.borderColor = colors.grid;
for (const chart of chartsByInstance) {
chart.options.scales.x.title.color = colors.text;
chart.options.scales.x.ticks.color = colors.text;
chart.options.scales.x.grid.color = colors.grid;
chart.options.scales.y.title.color = colors.text;
chart.options.scales.y.ticks.color = colors.text;
chart.options.scales.y.grid.color = colors.grid;
chart.update();
}
}
themeToggle.addEventListener('click', () => {
theme = theme === 'dark' ? 'light' : 'dark';
localStorage.setItem('fastfetch-benchmark-theme', theme);
updateTheme();
});
updateTheme();
const families = new Map();
for (const [key, rows] of groups) {
const arch = key.slice(key.lastIndexOf(' ') + 1);
const family = architectureFamily(arch);
if (!families.has(family)) families.set(family, []);
families.get(family).push({ key, rows });
}
const familyOrder = ['Intel', 'ARM', 'Other'];
const availableFamilies = familyOrder.filter(family => families.has(family));
for (const [index, family] of availableFamilies.entries()) {
const tab = document.createElement('button');
const panelId = `panel-${family.toLowerCase()}`;
tab.className = 'tab';
tab.type = 'button';
tab.id = `tab-${family.toLowerCase()}`;
tab.setAttribute('role', 'tab');
tab.textContent = family;
tab.setAttribute('aria-controls', panelId);
tab.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
tabs.appendChild(tab);
const panel = document.createElement('section');
panel.className = 'tab-panel';
panel.id = panelId;
panel.setAttribute('role', 'tabpanel');
panel.hidden = index !== 0;
panel.setAttribute('aria-labelledby', tab.id);
const title = document.createElement('h2');
title.className = 'platform-title';
title.textContent = `${family} processors`;
panel.appendChild(title);
const chartGrid = document.createElement('div');
chartGrid.className = 'charts';
panel.appendChild(chartGrid);
panels.appendChild(panel);
tab.addEventListener('click', () => {
for (const otherTab of tabs.children) otherTab.setAttribute('aria-selected', otherTab === tab ? 'true' : 'false');
for (const otherPanel of panels.children) otherPanel.hidden = otherPanel !== panel;
});
for (const { key, rows } of families.get(family).sort((a, b) => a.key.localeCompare(b.key))) {
rows.sort((a, b) => a.date.localeCompare(b.date));
const metrics = [...new Set(rows.flatMap(row => [...row.metrics.keys()]))].sort((a, b) => {
if (a === 'total process time') return -1;
if (b === 'total process time') return 1;
return a.localeCompare(b);
});
const section = document.createElement('article');
section.className = 'chart';
const chartHeader = document.createElement('div');
chartHeader.className = 'chart-header';
const chartTitle = document.createElement('h2');
chartTitle.textContent = key;
const chartActions = document.createElement('div');
chartActions.className = 'chart-actions';
const chartHint = document.createElement('span');
chartHint.textContent = `${rows.length} run${rows.length === 1 ? '' : 's'}`;
const fullscreen = document.createElement('button');
fullscreen.className = 'fullscreen';
fullscreen.type = 'button';
fullscreen.textContent = '⛶ Fullscreen';
fullscreen.addEventListener('click', () => {
if (document.fullscreenElement) document.exitFullscreen();
else section.requestFullscreen();
});
chartActions.append(chartHint, fullscreen);
chartHeader.append(chartTitle, chartActions);
const canvasWrap = document.createElement('div');
canvasWrap.className = 'canvas-wrap';
const canvas = document.createElement('canvas');
canvasWrap.appendChild(canvas);
section.append(chartHeader, canvasWrap);
chartGrid.appendChild(section);
const colorsForChart = chartColors();
const chart = new Chart(canvas, {
type: 'line',
data: {
labels: rows.map(row => row.sha.slice(0, 7)),
datasets: metrics.map((metric, index) => ({
label: metric,
data: rows.map(row => row.metrics.has(metric) ? row.metrics.get(metric) : null),
borderColor: colors[index % colors.length],
backgroundColor: colors[index % colors.length],
pointRadius: 3,
tension: 0.2,
spanGaps: true,
hidden: metric !== 'total process time'
}))
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: { display: false },
tooltip: {
enabled: false,
external: externalTooltipHandler,
callbacks: {
title: tooltipItems => {
const row = rows[tooltipItems[0].dataIndex];
return `${row.date} · ${row.sha}`;
}
}
}
},
scales: {
x: { title: { display: true, text: 'Date', color: colorsForChart.text }, ticks: { color: colorsForChart.text }, grid: { color: colorsForChart.grid } },
y: { beginAtZero: true, title: { display: true, text: 'Milliseconds', color: colorsForChart.text }, ticks: { color: colorsForChart.text }, grid: { color: colorsForChart.grid } }
}
}
});
chart._benchmarkRows = rows;
chartsByInstance.push(chart);
const legendActions = document.createElement('div');
legendActions.className = 'legend-actions';
const legend = document.createElement('div');
legend.className = 'legend';
const legendItems = [];
function refreshLegend() {
legendItems.forEach((item, metricIndex) => {
item.classList.toggle('is-hidden', !chart.isDatasetVisible(metricIndex));
});
}
function setAllLegends(visible) {
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, visible));
refreshLegend();
chart.update();
}
function addLegendAction(label, action) {
const button = document.createElement('button');
button.className = 'legend-action';
button.type = 'button';
button.textContent = label;
button.addEventListener('click', action);
legendActions.appendChild(button);
}
addLegendAction('Show all', () => setAllLegends(true));
addLegendAction('Toggle all', () => {
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, !chart.isDatasetVisible(metricIndex)));
refreshLegend();
chart.update();
});
addLegendAction('Reset', () => {
metrics.forEach((metric, metricIndex) => chart.setDatasetVisibility(metricIndex, metric === 'total process time'));
refreshLegend();
chart.update();
});
metrics.forEach((metric, metricIndex) => {
const item = document.createElement('button');
item.className = `legend-item${chart.isDatasetVisible(metricIndex) ? '' : ' is-hidden'}`;
item.type = 'button';
item.innerHTML = `<span class="legend-swatch" style="background:${colors[metricIndex % colors.length]}"></span>${metric}`;
item.addEventListener('click', () => {
chart.setDatasetVisibility(metricIndex, !chart.isDatasetVisible(metricIndex));
item.classList.toggle('is-hidden', !chart.isDatasetVisible(metricIndex));
chart.update();
});
legendItems.push(item);
legend.appendChild(item);
});
section.append(legendActions, legend);
}
}
if (!groups.size) {
tabs.hidden = true;
panels.innerHTML = '<p class="empty">No benchmark data available.</p>';
}
})();
</script>
</body>
</html>
+55
View File
@@ -0,0 +1,55 @@
name: Reusable Benchmark
on:
workflow_call:
permissions:
contents: write
jobs:
benchmark:
runs-on: ubuntu-latest
concurrency:
group: benchmark
cancel-in-progress: false
steps:
- name: checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: download benchmark data
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: benchmark-*
path: benchmarks
- name: merge benchmark data
run: jq -s 'add' benchmarks/*/benchmark.json > benchmark.json
- name: save benchmark dashboard
run: cp .github/benchmark-index.html "$RUNNER_TEMP/benchmark-index.html"
- name: store benchmark result
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
name: Fastfetch
tool: customSmallerIsBetter
output-file-path: benchmark.json
github-token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.sha }}
auto-push: true
summary-always: true
benchmark-data-dir-path: dev/bench
- name: publish benchmark dashboard
run: |
git fetch origin gh-pages
git switch --force-create gh-pages FETCH_HEAD
mkdir -p dev/bench
cp "$RUNNER_TEMP/benchmark-index.html" dev/bench/index.html
git config user.name github-action-benchmark
git config user.email github@users.noreply.github.com
git add dev/bench/index.html
if ! git diff --cached --quiet; then
git commit -m "Update benchmark dashboard"
git push origin gh-pages
fi
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
with:
operating_system: dragonflybsd
version: '6.4.2'
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
with:
operating_system: freebsd
version: '15.1'
+2 -2
View File
@@ -18,10 +18,10 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
with:
operating_system: haiku
version: 'r1beta5'
version: 'r1beta6'
shell: bash
cpu_count: 4
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
id: runcmd
with:
arch: armv7
+32 -4
View File
@@ -43,7 +43,7 @@ jobs:
run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- name: install required packages
run: sudo apt-get update && sudo apt-get install -y gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
run: sudo apt-get update && sudo apt-get install -y gcc-13 jq libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
- name: install linuxbrew packages
run: |
@@ -52,7 +52,7 @@ jobs:
- name: Initialize CodeQL
if: inputs.arch == 'amd64'
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
languages: c
@@ -64,7 +64,7 @@ jobs:
- name: perform CodeQL analysis
if: inputs.arch == 'amd64'
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
- name: list features
run: ./fastfetch --list-features
@@ -73,7 +73,29 @@ jobs:
run: time ./fastfetch -c presets/ci.jsonc --stat false
- name: run fastfetch --format json
run: time ./fastfetch -c presets/ci.jsonc --format json
run: |
{ time -p ./fastfetch -c presets/ci.jsonc --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
cat fastfetch.json
cat fastfetch.error >&2
cat fastfetch.time >&2
- name: prepare benchmark data
run: |
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
jq -e 'type == "array"' fastfetch.json > /dev/null
jq --arg platform "linux" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
map(select(.stat | type == "number") | {
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
unit: "ms",
value: .stat
}) + [{
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
unit: "ms",
value: ($real_seconds * 1000)
}]
' fastfetch.json > benchmark.json
cat benchmark.json
- name: run flashfetch
run: time ./flashfetch
@@ -105,3 +127,9 @@ jobs:
with:
name: fastfetch-linux-${{ inputs.arch }}
path: ./fastfetch-*.*
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-linux-${{ inputs.arch }}
path: benchmark.json
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
run: cmake --version
- name: configure project
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_PROCESSOR_OVERRIDE=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_VERSION=5.10.0 -DCMAKE_SYSTEM_PROCESSOR=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
- name: build project
run: cmake --build . --target package --verbose -j4
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
id: runcmd
with:
arch: ${{ inputs.arch }}
+50 -5
View File
@@ -26,13 +26,30 @@ jobs:
- name: uname -a
run: uname -a
- name: install required packages
- name: install required packages with MacPorts # Homebrew dropped Intel support
if: inputs.arch == 'amd64'
run: |
curl -fsSL https://github.com/macports/macports-base/releases/download/v2.12.6/MacPorts-2.12.6-15-Sequoia.pkg -o "$RUNNER_TEMP/MacPorts.pkg"
sudo installer -pkg "$RUNNER_TEMP/MacPorts.pkg" -target /
echo /opt/local/bin >> "$GITHUB_PATH"
echo /opt/local/sbin >> "$GITHUB_PATH"
export PATH=/opt/local/bin:/opt/local/sbin:$PATH
sudo port selfupdate
sudo port install vulkan-loader vulkan-headers MoltenVK ImageMagick7 chafa lua quickjs-ng jq
- name: install required packages with Homebrew
if: inputs.arch == 'aarch64'
run: |
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew update
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite vulkan-loader vulkan-headers molten-vk imagemagick chafa lua quickjs-ng jq
- name: configure project
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .
run: |
if [ "${{ inputs.arch }}" = "amd64" ]; then
export CMAKE_PREFIX_PATH=/opt/local
export PKG_CONFIG_PATH=/opt/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}
fi
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On .
- name: build project
run: cmake --build . --target package --verbose -j4
@@ -41,10 +58,32 @@ jobs:
run: ./fastfetch --list-features
- name: run fastfetch
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --stat false
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --stat false
- name: run fastfetch --format json
run: time ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan --format json
run: |
{ time -p ./fastfetch -c presets/ci.jsonc --structure-disabled vulkan:bluetooth:bluetoothradio --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
cat fastfetch.json
cat fastfetch.error >&2
cat fastfetch.time >&2
- name: prepare benchmark data
run: |
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
jq -e 'type == "array"' fastfetch.json > /dev/null
jq --arg platform "macos" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
map(select(.stat | type == "number") | {
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
unit: "ms",
value: .stat
}) + [{
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
unit: "ms",
value: ($real_seconds * 1000)
}]
' fastfetch.json > benchmark.json
cat benchmark.json
- name: run flashfetch
run: time ./flashfetch
@@ -60,3 +99,9 @@ jobs:
with:
name: fastfetch-macos-${{ inputs.arch }}
path: ./fastfetch-*.*
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-macos-${{ inputs.arch }}
path: benchmark.json
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
with:
operating_system: netbsd
version: '10.1'
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
with:
operating_system: omnios
version: 'r151058'
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@5ea7e8e4677bd726033a10b094ba1c5762b15dee # v1.3.0
uses: cross-platform-actions/action@faa0c6197e94aacf1c5956460152c8380d3560a5 # v1.5.0
with:
operating_system: openbsd
version: '7.9'
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: vmactions/solaris-vm@315163f088b66e55bbcc45928bd224d4973b2312 # v1.3.8
uses: vmactions/solaris-vm@96d8d976f9e67d82ec6c7e8ce9c1060731f9e21c # v1.3.9
with:
usesh: true
envs: 'CMAKE_BUILD_TYPE'
+31 -3
View File
@@ -49,7 +49,7 @@ jobs:
with:
msystem: ${{ inputs.msystem }}
update: true
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-jq mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
- name: print msys version
run: uname -a
@@ -70,7 +70,29 @@ jobs:
run: time ./fastfetch -c presets/ci.jsonc --stat false
- name: run fastfetch --format json
run: time ./fastfetch -c presets/ci.jsonc --format json
run: |
{ time -p ./fastfetch -c presets/ci.jsonc --format json > fastfetch.raw.json 2> fastfetch.error; } 2> fastfetch.time
sed -n '/^[[:space:]]*\[/,$p' fastfetch.raw.json > fastfetch.json
cat fastfetch.json
cat fastfetch.error >&2
cat fastfetch.time >&2
- name: prepare benchmark data
run: |
real_seconds=$(awk '$1 == "real" { print $2 }' fastfetch.time)
jq -e 'type == "array"' fastfetch.json > /dev/null
jq --arg platform "windows" --arg arch "${{ inputs.arch }}" --argjson real_seconds "$real_seconds" '
map(select(.stat | type == "number") | {
name: ("fastfetch " + $platform + " " + $arch + " module " + .type),
unit: "ms",
value: .stat
}) + [{
name: ("fastfetch " + $platform + " " + $arch + " total process time"),
unit: "ms",
value: ($real_seconds * 1000)
}]
' fastfetch.json > benchmark.json
cat benchmark.json
- name: run flashfetch
run: time ./flashfetch
@@ -94,7 +116,7 @@ jobs:
- if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'
name: submit signing request
uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2.2
uses: signpath/github-action-submit-signing-request@c92b958760219087e01f8d67a1669ed57afe2627 # v2.3
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '${{ vars.SIGNPATH_ORG_ID }}'
@@ -116,3 +138,9 @@ jobs:
name: fastfetch-windows-${{ inputs.arch }}
path: ./fastfetch-windows-${{ inputs.arch }}.*
overwrite: true
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-windows-${{ inputs.arch }}
path: benchmark.json
+12 -2
View File
@@ -138,7 +138,6 @@ jobs:
uses: ./.github/workflows/build-dragonfly-amd64.yml
haiku-amd64:
if: false # Disabled because the Haiku build is currently broken
needs: no-features-test
name: Haiku-amd64
permissions:
@@ -176,6 +175,17 @@ jobs:
secrets:
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
benchmark:
if: github.event_name == 'push' && github.ref == 'refs/heads/dev' && github.repository == 'fastfetch-cli/fastfetch'
name: Benchmark
needs:
- linux-hosts
- macos-hosts
- windows-hosts
permissions:
contents: write
uses: ./.github/workflows/build-benchmark.yml
release:
if: github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'fastfetch-cli/fastfetch'
name: Release
@@ -193,7 +203,7 @@ jobs:
- dragonfly-amd64
- solaris-amd64
- omnios-amd64
# - haiku-amd64
- haiku-amd64
- windows-hosts
permissions:
contents: write
+3
View File
@@ -12,3 +12,6 @@ fastfetch.kdev4
*.user
*.user.*
*.swp
*.log
/*.zh.md
/.*-ai/
+75 -10
View File
@@ -1,3 +1,68 @@
# Unreleased
Features:
* Added CPU name and frequency detection support on SPARC. (CPU, Linux)
# 2.68.1
Changes:
* The DE / WM / LM modules now reports the full name "Desktop Environment" / "Window Manager" / "Login Manager" instead of the abbreviations.
Features:
* Improved Login Manager (LM) module
* Added support on macOS, Windows and POSIX systems without systemd (by process list enumeration).
* Reports pretty name on Linux.
* Added Top module to print processes with the highest CPU, memory or disk I/O usage. (Top)
* Added module key localization support. (General)
* Added a new option `--key-language <?lang>` (`display.key.language: "<?lang>"` in JSON config). When left empty, it defaults to the system locale.
* See `fastfetch -h key-language` for all supported languages.
* Exposed in custom key format as `{module-name}`.
* Improved Wi-Fi module
* Added Wifi channel width detection, exposed via `{channel-width}` in custom format (reports 0 when not supported).
* Improved Wifi channel frequency accuracy and fortified detection on Windows, macOS.
* Added Wifi detection support on SunOS, Haiku.
* Added Battery detection support on SunOS. (Battery, SunOS)
* Added terminal font size detection support on Termux. (TerminalFont, Android)
* Improved GTK theme/icons/font detection on standalone WMs such as Hyprland, sway, niri and i3. (Theme, Linux)
* Improved fish completion scripts to print enum descriptions. (Completion)
* Enabled Wayland support on Android. (DisplayServer, Android)
* Added DE detection support for ASUS Zenfone. (DE, Android)
* Added new ARM SoCs (CPU, Linux / Android)
Bugfixes:
* Fixed I/O rate calculation precision in DiskIO and NetIO, and prevented division by zero. (DiskIO / NetIO)
* Fixed the fast path of ash version detection. (Shell)
* Fixed memory usage detection support on x86-32 FreeBSD (Memory, FreeBSD)
* Note: Although 32-bit systems are still supported, they are deprecated and barely tested. Users are encouraged to upgrade to 64-bit systems.
* Some internal cleanups and optimizations.
* Attempted to fix potential issues on big-endian systems
* Not tested due to lack of access to big-endian hardware.
Logos:
* Added macOS_old
* Added Omarchy, Omarchy2
# 2.67.1
Bugfixes:
* Fixed a `Symbol not found` error when running on macOS 10.15. (#2499, Codec, macOS)
* Fixed Alacritty font size never being parsed from the TOML config. (#2501, TerminalFont)
* Fixed bright colors incorrectly being active when the terminal uses a light theme. (#2509, Display)
* Fixed Ghostty terminal font detection (TerminalFont)
* Now tries to load the config with the `.ghostty` extension. (#2500)
* Fixed `font-family` fallback order. (#2510)
* Some internal cleanups and optimizations.
Features:
* Added a C23 compiler check so the build process gracefully exits during CMake configuration if the compiler is unsupported. (CMake)
* Added compatibility workarounds for Apple Clang 16 to fix building on macOS 14. (CMake, macOS)
* Reported real Android version codenames (e.g., "Baklava", "Vanilla Ice Cream") instead of "REL". (OS, Android)
* Added Loadavg detection support on Haiku R1B6. (Loadavg, Haiku)
* Enabled image logo support in the Zellij terminal multiplexer. (#2507, Logo)
* Improved Locale detection on Windows (Locale, Windows)
* Now respects `LC_ALL` and `LANG` environment variables if set for better compatibility with MSYS2/cygwin.
* Appends the active system Code Page (e.g., `.UTF-8` or `.Windows-1252`) to the default system locale fallback.
# 2.67.0
Changes:
@@ -31,12 +96,12 @@ Bugfixes:
* Fixed invalid URL parsing in the PublicIP module. (PublicIP)
* Correctly reported virtual GPUs on Windows. (#2461, GPU, Windows)
* Added a size limit for network responses to prevent excessive memory usage and mitigate potential attacks. (PublicIP / Weather)
* Fixed Ubuntu Studio Core detection (OS, Linux)
* Fixed Ubuntu Studio Core detection. (OS, Linux)
* Relaxed the HTTP response check so that both `HTTP/1.0` and `HTTP/1.1` responses are accepted when fetching data over the network. (PublicIP / Weather)
* Various internal cleanups and optimizations:
* Fixed multiple memory leaks (Separator, Camera, Codec, Display)
* Added integer overflow checks to the string buffer implementation
* Various code cleanups and compiler warning fixes
* Fixed multiple memory leaks (Separator, Camera, Codec, Display).
* Added integer overflow checks to the string buffer implementation.
* Various code cleanups and compiler warning fixes.
Logos:
* Removed Hypros, MagpieOS, Furreto, EmperorOS and Magix
@@ -49,9 +114,9 @@ Changes:
Bugfixes:
* Fixed compatibility with WSL 2.9.3 (#2433, WM, Linux)
* Fixed accurate AMD GPU names are not queried in `driverSpecific` mode (GPU, Linux)
* Fixed AMD GPU names not being queried accurately in `driverSpecific` mode (GPU, Linux)
* Fixed GPU core counting for Apple Silicon GPU on Asahi Linux (GPU, Linux)
* Some internal cleanups and optimizations
* Some internal cleanups and optimizations.
Features:
* Added Astra Linux version detection (OS, Linux)
@@ -784,7 +849,7 @@ Features:
* Added support for interface speed detection on SunOS (LocalIP, SunOS)
* Added detection support for Xlibre (#1888, WM, Linux)
* Improved the accuracy of color detection (Cursor, macOS)
* Improved the proformance of `Nix` package manager detection on macOS by porting optimizations form Linux port (#1893, Packages, macOS)
* Improved the performance of `Nix` package manager detection on macOS by porting optimizations from the Linux port (#1893, Packages, macOS)
Bugfixes:
* Fixed custom object inheriting a key from the previous custom object if the key is blank (#1477)
@@ -1295,7 +1360,7 @@ Features:
* Change package manager name of NetBSD from `pkg` to `pkgsrc` (#1515, Packages, NetBSD)
* Detect SOC name on RISCV (#1519, CPU, Linux)
* Report marketing name of new QS8Es (CPU, Android)
* Acquire acquire more os info from lsb-release if missing from os-release (#1521)
* Acquire more OS info from lsb-release if missing from os-release (#1521)
* CMake: add option `-DCUSTOM_LSB_RELEASE_PATH` to specify the path of `lsb-release` file
* `-DCUSTOM_OS_RELEASE_PATH` has been supported since `v2.11.4`
* Report more SOC names on Android (CPU, Android)
@@ -1763,7 +1828,7 @@ Logos:
Bugfixes:
* Fix bad Intel Arc GPU name detection, which was supposed to be fixed in the last version but the change was reverted accidentally (#1177, GPU, Linux)
* Fix arm32 CPU name detection no longer work. Regression of 2.21.2 (CPU, Linux)
* Fix arm32 CPU name detection no longer working. Regression of 2.21.2 (CPU, Linux)
# 2.21.2
@@ -3258,7 +3323,7 @@ Features:
* Enhance `--percent-type` to allow hiding other texts (#387)
* Add Wifi module support for Linux
* Detect scaled resolutions (Windows, macOS)
* Optimise font module printing (Windows)
* Optimize font module printing (Windows)
* Detect pacman package count inside MSYS2 environment (Windows)
* Add Wifi / Battery module support for Android
* Disk name support for Linux
+92 -113
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
project(fastfetch
VERSION 2.67.0
VERSION 2.68.1
LANGUAGES C
DESCRIPTION "Fast neofetch-like system information tool"
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
@@ -9,9 +9,6 @@ project(fastfetch
set(PROJECT_LICENSE "MIT license")
if(DEFINED CMAKE_SYSTEM_PROCESSOR_OVERRIDE) # Used by github actions for i686 build
set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_SYSTEM_PROCESSOR_OVERRIDE} CACHE INTERNAL "")
endif()
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_SYSTEM_PROCESSOR)
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
set(CMAKE_SYSTEM_PROCESSOR "amd64")
@@ -62,6 +59,7 @@ if(NOT PKG_CONFIG_FOUND)
endif()
include(CheckIncludeFile)
include(CheckFunctionExists)
if(LINUX)
CHECK_INCLUDE_FILE("linux/version.h" HAVE_LINUX_VERSION_H)
@@ -77,7 +75,7 @@ endif()
include(CMakeDependentOption)
cmake_dependent_option(ENABLE_VULKAN "Enable vulkan" ON "LINUX OR APPLE OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
cmake_dependent_option(ENABLE_WAYLAND "Enable wayland-client" ON "ANDROID OR LINUX OR FreeBSD OR OpenBSD OR NetBSD" OFF)
cmake_dependent_option(ENABLE_XCB_RANDR "Enable xcb-randr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_XRANDR "Enable xrandr" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_DRM "Enable libdrm" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF)
@@ -127,13 +125,24 @@ if(APPLE)
option(ENABLE_APPLE_MEMSIZE_USABLE "Use usable memory size as total memory size in Memory module, to match other systems" OFF)
endif()
file(GLOB FF_MODULE_DIRS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/*/")
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/${FF_MODULE_DIR}")
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
# A module is a directory `src/modules/<module>/` that contains `<module>.c`.
# Globbing the sources rather than the directories skips empty directories and
# other leftovers, and makes `CONFIGURE_DEPENDS` react to added or removed sources.
file(GLOB FF_MODULE_SRCS CONFIGURE_DEPENDS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src/modules" "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/*/*.c")
set(FF_MODULE_DIRS "")
foreach(FF_MODULE_SRC ${FF_MODULE_SRCS})
get_filename_component(FF_MODULE_DIR "${FF_MODULE_SRC}" DIRECTORY)
get_filename_component(FF_MODULE_NAME "${FF_MODULE_SRC}" NAME_WE)
if("${FF_MODULE_DIR}" STREQUAL "${FF_MODULE_NAME}")
list(APPEND FF_MODULE_DIRS "${FF_MODULE_DIR}")
endif()
endforeach()
unset(FF_MODULE_SRCS)
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
endforeach()
set(BINARY_LINK_TYPE_OPTIONS dlopen dynamic static)
set(BINARY_LINK_TYPE dlopen CACHE STRING "How to link fastfetch")
@@ -483,6 +492,7 @@ set(LIBFASTFETCH_SRC
src/detection/physicalmemory/physicalmemory.c
src/detection/publicip/publicip.c
src/detection/terminaltheme/terminaltheme.c
src/detection/top/top.c
src/detection/terminalfont/terminalfont.c
src/detection/terminalshell/terminalshell.c
src/detection/version/version.c
@@ -494,87 +504,18 @@ set(LIBFASTFETCH_SRC
src/logo/image/im7.c
src/logo/image/image.c
src/logo/logo.c
src/modules/battery/battery.c
src/modules/bios/bios.c
src/modules/bluetooth/bluetooth.c
src/modules/bluetoothradio/bluetoothradio.c
src/modules/board/board.c
src/modules/bootmgr/bootmgr.c
src/modules/brightness/brightness.c
src/modules/break/break.c
src/modules/btrfs/btrfs.c
src/modules/camera/camera.c
src/modules/chassis/chassis.c
src/modules/codec/codec.c
src/modules/colors/colors.c
src/modules/cpu/cpu.c
src/modules/cpucache/cpucache.c
src/modules/cpuusage/cpuusage.c
src/modules/cursor/cursor.c
src/modules/custom/custom.c
src/modules/command/command.c
src/modules/datetime/datetime.c
src/modules/de/de.c
src/modules/disk/disk.c
src/modules/diskio/diskio.c
src/modules/dns/dns.c
src/modules/editor/editor.c
src/modules/font/font.c
src/modules/gpu/gpu.c
src/modules/host/host.c
src/modules/icons/icons.c
src/modules/initsystem/initsystem.c
src/modules/gamepad/gamepad.c
src/modules/kernel/kernel.c
src/modules/keyboard/keyboard.c
src/modules/lm/lm.c
src/modules/loadavg/loadavg.c
src/modules/locale/locale.c
src/modules/localip/localip.c
src/modules/logo/logo.c
src/modules/memory/memory.c
src/modules/monitor/monitor.c
src/modules/netio/netio.c
src/modules/opencl/opencl.c
src/modules/opengl/opengl.c
src/modules/os/os.c
src/modules/packages/packages.c
src/modules/physicaldisk/physicaldisk.c
src/modules/physicalmemory/physicalmemory.c
src/modules/processes/processes.c
src/modules/player/player.c
src/modules/poweradapter/poweradapter.c
src/modules/publicip/publicip.c
src/modules/display/display.c
src/modules/separator/separator.c
src/modules/shell/shell.c
src/modules/sound/sound.c
src/modules/swap/swap.c
src/modules/media/media.c
src/modules/mouse/mouse.c
src/modules/terminal/terminal.c
src/modules/terminaltheme/terminaltheme.c
src/modules/terminalfont/terminalfont.c
src/modules/terminalsize/terminalsize.c
src/modules/theme/theme.c
src/modules/title/title.c
src/modules/tpm/tpm.c
src/modules/uptime/uptime.c
src/modules/users/users.c
src/modules/version/version.c
src/modules/vulkan/vulkan.c
src/modules/wallpaper/wallpaper.c
src/modules/weather/weather.c
src/modules/wifi/wifi.c
src/modules/wm/wm.c
src/modules/wmtheme/wmtheme.c
src/modules/zpool/zpool.c
src/modules/modules.c
src/options/display.c
src/options/logo.c
src/options/general.c
)
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
list(APPEND LIBFASTFETCH_SRC
src/modules/${FF_MODULE_DIR}/${FF_MODULE_DIR}.c
)
endforeach()
if(LINUX)
list(APPEND LIBFASTFETCH_SRC
src/common/impl/dbus.c
@@ -650,6 +591,7 @@ if(LINUX)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_linux.c
src/detection/tpm/tpm_linux.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -690,6 +632,13 @@ elseif(ANDROID)
src/detection/physicalmemory/physicalmemory_nosupport.c
src/detection/diskio/diskio_linux.c
src/detection/displayserver/displayserver_android.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
src/detection/displayserver/linux/wayland/wp-color-management-v1-protocol.c
src/detection/displayserver/linux/wayland/kde-output-device-v2-protocol.c
src/detection/displayserver/linux/wayland/xdg-output-unstable-v1-protocol.c
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/wmde.c
src/detection/displayserver/linux/xcb.c
@@ -725,6 +674,7 @@ elseif(ANDROID)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -801,7 +751,6 @@ elseif(FreeBSD)
src/detection/os/os_linux.c
src/detection/packages/packages_bsd.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_bsd.c
src/detection/gtk_qt/qt.c
src/detection/sound/sound_bsd.c
src/detection/swap/swap_bsd.c
@@ -821,11 +770,15 @@ elseif(FreeBSD)
if(DragonFly)
list(APPEND LIBFASTFETCH_SRC
src/detection/bluetooth/bluetooth_nosupport.c
src/detection/processes/processes_dbsd.c
src/detection/top/top_dbsd.c
src/detection/wifi/wifi_nosupport.c
)
else()
list(APPEND LIBFASTFETCH_SRC
src/detection/bluetooth/bluetooth_bsd.c
src/detection/processes/processes_bsd.c
src/detection/top/top_bsd.c
src/detection/wifi/wifi_bsd.c
)
endif()
@@ -903,6 +856,7 @@ elseif(NetBSD)
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/top/top_nbsd.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_linux.c
src/detection/wallpaper/wallpaper_linux.c
@@ -963,7 +917,7 @@ elseif(OpenBSD)
src/detection/gpu/gpu_bsddrm.c
src/detection/gtk_qt/gtk.c
src/detection/host/host_obsd.c
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_linux.c
src/detection/icons/icons_linux.c
src/detection/initsystem/initsystem_linux.c
src/detection/keyboard/keyboard_nosupport.c
@@ -989,6 +943,7 @@ elseif(OpenBSD)
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/tpm/tpm_nosupport.c
src/detection/top/top_obsd.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_obsd.c
src/detection/wallpaper/wallpaper_linux.c
@@ -1022,7 +977,7 @@ elseif(APPLE)
src/detection/cpu/cpu_apple.c
src/detection/cpucache/cpucache_apple.c
src/detection/cpuusage/cpuusage_apple.c
src/detection/cursor/cursor_apple.m
src/detection/cursor/cursor_apple.c
src/detection/disk/disk_bsd.c
src/detection/dns/dns_apple.c
src/detection/physicaldisk/physicaldisk_apple.c
@@ -1034,7 +989,7 @@ elseif(APPLE)
src/detection/icons/icons_nosupport.c
src/detection/initsystem/initsystem_linux.c
src/detection/keyboard/keyboard_apple.c
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_apple.m
src/detection/loadavg/loadavg_bsd.c
src/detection/libc/libc_apple.c
src/detection/locale/locale_linux.c
@@ -1049,13 +1004,14 @@ elseif(APPLE)
src/detection/packages/packages_apple.c
src/detection/packages/packages_nix.c
src/detection/poweradapter/poweradapter_apple.c
src/detection/processes/processes_bsd.c
src/detection/processes/processes_apple.c
src/detection/sound/sound_apple.c
src/detection/swap/swap_apple.c
src/detection/terminalfont/terminalfont_apple.m
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_apple.c
src/detection/top/top_apple.c
src/detection/tpm/tpm_apple.c
src/detection/uptime/uptime_bsd.c
src/detection/users/users_linux.c
@@ -1063,7 +1019,7 @@ elseif(APPLE)
src/detection/wifi/wifi_apple.m
src/detection/wm/wm_apple.m
src/detection/de/de_nosupport.c
src/detection/wmtheme/wmtheme_apple.m
src/detection/wmtheme/wmtheme_apple.c
src/detection/camera/camera_apple.m
)
# CMAKE_SYSTEM_PROCESSOR has been normalized before
@@ -1127,7 +1083,7 @@ elseif(WIN32)
src/detection/initsystem/initsystem_windows.c
src/detection/keyboard/keyboard_windows.c
src/detection/libc/libc_windows.cpp
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_windows.c
src/detection/loadavg/loadavg_nosupport.c
src/detection/locale/locale_windows.c
src/detection/localip/localip_windows.c
@@ -1148,6 +1104,7 @@ elseif(WIN32)
src/detection/terminalshell/terminalshell_windows.c
src/detection/terminalsize/terminalsize_windows.c
src/detection/theme/theme_windows.c
src/detection/top/top_windows.c
src/detection/tpm/tpm_windows.c
src/detection/uptime/uptime_windows.c
src/detection/users/users_windows.c
@@ -1168,7 +1125,7 @@ elseif(SunOS)
src/common/impl/FFPlatform_unix.c
src/common/impl/binary_linux.c
src/common/impl/kmod_sunos.c
src/detection/battery/battery_nosupport.c
src/detection/battery/battery_sunos.c
src/detection/bios/bios_windows.c
src/detection/board/board_windows.c
src/detection/bootmgr/bootmgr_nosupport.c
@@ -1209,7 +1166,7 @@ elseif(SunOS)
src/detection/initsystem/initsystem_linux.c
src/detection/keyboard/keyboard_nosupport.c
src/detection/libc/libc_nosupport.c
src/detection/lm/lm_nosupport.c
src/detection/lm/lm_linux.c
src/detection/loadavg/loadavg_sunos.c
src/detection/locale/locale_linux.c
src/detection/localip/localip_linux.c
@@ -1222,7 +1179,7 @@ elseif(SunOS)
src/detection/os/os_sunos.c
src/detection/packages/packages_sunos.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_linux.c
src/detection/processes/processes_sunos.c
src/detection/gtk_qt/qt.c
src/detection/sound/sound_sunos.c
src/detection/swap/swap_sunos.c
@@ -1230,11 +1187,12 @@ elseif(SunOS)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_sunos.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_sunos.c
src/detection/users/users_linux.c
src/detection/wallpaper/wallpaper_linux.c
src/detection/wifi/wifi_nosupport.c
src/detection/wifi/wifi_sunos.c
src/detection/wm/wm_nosupport.c
src/detection/de/de_linux.c
src/detection/wmtheme/wmtheme_linux.c
@@ -1281,7 +1239,6 @@ elseif(Haiku)
src/detection/keyboard/keyboard_haiku.cpp
src/detection/libc/libc_nosupport.c
src/detection/lm/lm_nosupport.c
src/detection/loadavg/loadavg_nosupport.c
src/detection/locale/locale_linux.c
src/detection/localip/localip_linux.c
src/detection/gamepad/gamepad_haiku.cpp
@@ -1301,16 +1258,24 @@ elseif(Haiku)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_nosupport.c
src/detection/top/top_haiku.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_haiku.c
src/detection/users/users_linux.c
src/detection/wallpaper/wallpaper_haiku.cpp
src/detection/wifi/wifi_nosupport.c
src/detection/wifi/wifi_haiku.cpp
src/detection/wm/wm_nosupport.c
src/detection/de/de_nosupport.c
src/detection/wmtheme/wmtheme_haiku.cpp
src/detection/camera/camera_nosupport.c
)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} bsd)
check_function_exists(getloadavg HAVE_GETLOADAVG)
if(HAVE_GETLOADAVG)
list(APPEND LIBFASTFETCH_SRC src/detection/loadavg/loadavg_sunos.c)
else()
list(APPEND LIBFASTFETCH_SRC src/detection/loadavg/loadavg_nosupport.c)
endif()
elseif(GNU)
list(APPEND LIBFASTFETCH_SRC
src/common/impl/dbus.c
@@ -1370,7 +1335,7 @@ elseif(GNU)
src/detection/packages/packages_linux.c
src/detection/packages/packages_nix.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_linux.c
src/detection/processes/processes_gnu.c
src/detection/gtk_qt/qt.c
src/detection/sound/sound_linux.c
src/detection/swap/swap_linux.c
@@ -1378,6 +1343,7 @@ elseif(GNU)
src/detection/terminalshell/terminalshell_linux.c
src/detection/terminalsize/terminalsize_linux.c
src/detection/theme/theme_linux.c
src/detection/top/top_gnu.c
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
@@ -1428,7 +1394,6 @@ if(WIN32)
endif()
endif()
endif()
include(CheckFunctionExists)
if(NOT WIN32)
check_function_exists(pipe2 HAVE_PIPE2)
endif()
@@ -1458,21 +1423,33 @@ add_library(libfastfetch OBJECT
)
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/modules/${FF_MODULE_DIR}")
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
if(MODULE_DISABLE_${FF_MODULE_UPPER})
target_compile_definitions(libfastfetch PUBLIC FF_MODULE_DISABLE_${FF_MODULE_UPPER}=1)
endif()
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
if(MODULE_DISABLE_${FF_MODULE_UPPER})
target_compile_definitions(libfastfetch PUBLIC FF_MODULE_DISABLE_${FF_MODULE_UPPER}=1)
endif()
endforeach()
include(CheckCSourceCompiles)
check_c_source_compiles("int main(void){int arr[1];return _Countof(arr);}" COMPILER_SUPPORTS_COUNT_OF)
if(COMPILER_SUPPORTS_COUNT_OF)
message(STATUS "_Countof is supported by the compiler")
message(STATUS "C2Y _Countof is supported by the compiler")
target_compile_definitions(libfastfetch PUBLIC FF_SUPPORTS_COUNT_OF=1)
else()
message(STATUS "_Countof is NOT supported by the compiler")
message(STATUS "C2Y _Countof is NOT supported by the compiler")
endif()
# Test C23 compiler support
check_c_source_compiles("enum my_enum: long{MY_ENUM_ZERO}; void test(void*p){} int main(int,const char**){[[gnu::cleanup(test)]] void* x=nullptr; return (int)MY_ENUM_ZERO;}" COMPILER_SUPPORTS_C23)
if(NOT COMPILER_SUPPORTS_C23)
message(FATAL_ERROR "Compiler does not support C23. Please use a newer compiler.")
endif()
# A hack for AppleClang 17, which supports most C23 features we use except for the `auto` keyword.
# See https://github.com/Homebrew/homebrew-core/pull/297403
check_c_source_compiles("int main(void){auto v = 12.34; return (int)v;}" COMPILER_SUPPORTS_AUTO)
if(NOT COMPILER_SUPPORTS_AUTO)
message(WARNING "Compiler does not support C23 'auto' keyword. A workaround will be applied.")
target_compile_definitions(libfastfetch PUBLIC $<$<COMPILE_LANGUAGE:C>:auto=__auto_type>)
endif()
if(yyjson_FOUND)
@@ -1481,10 +1458,11 @@ if(yyjson_FOUND)
# `target_link_libraries(yyjson::yyjson)` sets rpath implicitly
endif()
# Used for dlopen finding dylibs installed by homebrew
# `/opt/homebrew/lib` is not on in dlopen search path by default
# Used for dlopen finding dylibs installed by package managers
# `/opt/homebrew/lib`, `/usr/local/lib` and `/opt/local/lib` are not on the
# dlopen search path by default
if(APPLE AND BINARY_LINK_TYPE STREQUAL "dlopen")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,/opt/homebrew/lib -Wl,-rpath,/usr/local/lib -Wl,-rpath,/opt/local/lib")
endif()
if(ANDROID)
@@ -1555,7 +1533,7 @@ elseif(Haiku)
target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE)
elseif(GNU)
# On Hurd PATH_MAX is not defined. Set an arbitrary limit as workaround.
target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE PATH_MAX=4096 O_PATH=0)
target_compile_definitions(libfastfetch PUBLIC _GNU_SOURCE PATH_MAX=4096 O_PATH=O_NORW)
endif()
if(APPLE)
@@ -1953,7 +1931,6 @@ elseif(FreeBSD)
elseif(OpenBSD)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE "kvm"
PRIVATE "sndio"
PRIVATE "util"
)
@@ -1975,6 +1952,7 @@ elseif(SunOS)
elseif(GNU)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE "ps"
)
elseif(ANDROID)
target_link_libraries(libfastfetch
@@ -2006,6 +1984,7 @@ elseif(Haiku)
PRIVATE "GL"
PRIVATE "be"
PRIVATE "gnu"
PRIVATE "bsd"
)
endif()
+797
View File
@@ -0,0 +1,797 @@
# Contributing to fastfetch
<details>
<summary>
Disclaimer
</summary>
This document is generated by AI and reviewed by humans. It is primarily intended for AI-assisted development, while remaining useful to human developers.
</details>
Thank you for your interest in fastfetch. This document covers building, architecture, how to add a module or a logo, code style, commit conventions, and the pull request workflow.
fastfetch is a system information tool written in C23, supporting Linux, macOS, Windows, the BSDs, Solaris, Haiku and Android. The project places strong emphasis on **startup time** and on keeping **dependencies optional**; many design decisions only make sense under that constraint, and this document returns to it repeatedly.
---
## Table of contents
- [Contributor quick reference](#contributor-quick-reference)
- [Getting started](#getting-started)
- [Code map](#code-map)
- [Core architecture: modules vs. detection](#core-architecture-modules-vs-detection)
- [Module registration](#module-registration)
- [Walkthrough: adding a module](#walkthrough-adding-a-module)
- [Walkthrough: adding a logo](#walkthrough-adding-a-logo)
- [Platform conventions](#platform-conventions)
- [Code style](#code-style)
- [Commit messages](#commit-messages)
- [Changelog](#changelog)
- [Tests](#tests)
- [Pull requests](#pull-requests)
- [Pitfalls](#pitfalls)
---
## Contributor quick reference
| Task | Start here | Verify with |
|---|---|---|
| Build and run fastfetch | `run.sh`, `CMakeLists.txt` | `./run.sh` |
| Add a module | `src/modules/<name>/`, `src/detection/<name>/`, `src/modules/modules.c`, `CMakeLists.txt` | `cmake -B build && cmake --build build -j` |
| Add a platform implementation | `src/detection/<name>/<name>_<platform>.c`, the matching platform block in `CMakeLists.txt` | Build on the target platform or CI |
| Add an ASCII logo | `src/logo/ascii/<letter>/<name>.txt`, matching `<letter>.inc` | `./build/fastfetch --logo <name>` |
| Change formatting or JSON output | `src/modules/<name>/<name>.c` | `./build/fastfetch -s <name> --format json` |
| Change shared formatting or containers | `src/common/format.h`, `src/common/color.h`, `src/common/FFstrbuf.h` | Build and run the matching test |
| Run the test suite | `tests/`, `build/` | `cd build && ctest --output-on-failure` |
For a typical code change, the shortest useful iteration is: configure, build the `fastfetch` target, run the affected module, then run the tests. If you add a directory or a generated input, re-run `cmake -B build` so that CMake refreshes its file globs.
---
## Getting started
### Building
The quickest way is `run.sh` in the repository root: it creates `build/`, configures, compiles and runs the binary.
```sh
./run.sh # build and run
./run.sh --format json # arguments are forwarded to fastfetch
```
Manual build:
```sh
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build --target fastfetch -j$(nproc)
./build/fastfetch
```
The default build type is `RelWithDebInfo`. LTO is enabled whenever `ENABLE_LTO=ON` **and** `CMAKE_BUILD_TYPE != Debug`, so the default configuration enables it and only `Debug` builds omit it. This is significant because LTO here is not merely an optimization: it is what removes the code of disabled modules. See [Pitfalls](#pitfalls).
### Common build options
| Option | Default | Notes |
|---|---|---|
| `CMAKE_BUILD_TYPE` | `RelWithDebInfo` | LTO is on for anything but `Debug` |
| `BUILD_TESTS` | `OFF` | Builds the unit tests in `tests/` |
| `BUILD_FLASHFETCH` | `ON` | Also builds the stripped-down `flashfetch` |
| `BINARY_LINK_TYPE` | `dlopen` | `dlopen` / `dynamic` / `static` |
| `ENABLE_ASAN` | `OFF` | Address Sanitizer |
| `ENABLE_LTO` | `ON` | Link-time optimization |
| `MODULE_DISABLE_<NAME>` | `OFF` | Disable a module, e.g. `MODULE_DISABLE_GPU=ON` |
| `SET_TWEAK` | `ON` | Appends a tweak to the dev version; turned off for releases |
`BINARY_LINK_TYPE=dlopen` (the default) is a deliberate design choice: optional dependencies (Vulkan, Wayland, DBus, ImageMagick, chafa, …) are loaded at runtime, so a missing library never prevents startup. When adding a third-party dependency, use the dlopen helpers in `common/library.h` — do not link it directly.
For a minimal-dependency build, see `.github/workflows/build-no-features-test.yml`:
```sh
cmake -DBUILD_TESTS=On -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_X11=OFF \
-DENABLE_DBUS=OFF -DENABLE_ZLIB=OFF ... .
```
### Dependencies
Required: CMake ≥ 3.21 and a C23 compiler (GCC, Clang or MSVC). Everything else is optional.
Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, pulse, ddcutil, elf, libzfs, and more.
---
## Code map
```
src/
├── fastfetch.c entry point: CLI parsing, module dispatch, main loop (36 KB)
├── flashfetch.c entry point for the stripped-down build
├── options/ global (non-module) config: general / display / logo
├── modules/ module layer — module formatting and output
├── detection/ detection layer — platform-specific data retrieval
├── common/ infrastructure: FFstrbuf, FFlist, formatting, printing, networking
├── logo/ 530 ASCII logos plus the image logo backends
└── 3rdparty/ yyjson, widecharwidth, display-library
```
Flow of control:
```
fastfetch.c → options/ (global configuration)
→ modules/ (76 modules, formatting and output)
→ detection/ (one platform implementation each, raw data)
→ common/ (infrastructure)
```
The number of module directories does not match the number of detection directories, which is expected. The following relationships are structural rather than a fixed inventory:
- **13 modules have no detection directory** — they are either pure layout (`break`, `separator`, `colors`, `title`, `logo`) or reuse another module's detection result (`display`, `monitor`, `kernel`, `shell`, `terminal`, `player`, `custom`, `datetime`).
- **4 detection directories serve modules with different names** — `displayserver` (→ `display`, `monitor`), `gtk_qt` (→ `theme`, `icons`, `font`, `cursor`), `terminalshell` (→ `terminal`, `shell`) and `libc`.
Therefore, do not assume that `modules/<x>/` always has a matching `detection/<x>/`.
### Layer boundaries
| Layer | Owns | Must not |
|---|---|---|
| `options/` | **Global** config (colors, logo, threading, timeouts) | hold per-module options |
| `modules/` | formatting, printing, JSON serialization | read `/proc`, call Win32 APIs, parse sysfs |
| `detection/` | cross-platform data retrieval, clean result structs | print anything, read display config |
| `common/` | general-purpose utilities, no business logic | depend on a specific module |
A simple check: **no file under `detection/` should contain `printf` or read `instance.config`.** Symmetrically, no file under `modules/` should contain `#ifdef __linux__`.
---
## Core architecture: modules vs. detection
This is the most important convention in the codebase. Every feature consists of a fixed set of files; using CPU as the example:
```
modules/cpu/option.h module option struct
modules/cpu/cpu.c formatting and output
detection/cpu/cpu.h platform-independent interface
detection/cpu/cpu_linux.c ┐
detection/cpu/cpu_apple.c │
detection/cpu/cpu_windows.c ├─ platform implementations, one linked per build
detection/cpu/cpu_bsd.c │
detection/cpu/cpu_nosupport.c ┘
```
The interface is deliberately minimal, typically consisting of only two declarations:
```c
typedef struct FFCPUResult { ... } FFCPUResult; // result struct
const char* ffDetectCPU(const FFCPUOptions* options, FFCPUResult* cpu);
```
The `const char*` return value is an **error string**; `nullptr` means success. This pattern is used throughout `detection/` — please keep it consistent.
Helper functions are only exposed when they are genuinely shared between platform implementations (as in `cpu.h`: `ffCPUAppleCodeToName`, `ffCPUDetectByCpuid`).
**The benefit of this separation:** adding a platform never touches `modules/`; fixing output formatting never touches `detection/`.
---
## Module registration
### The descriptor
Every module exports one `FFModuleBaseInfo` (defined in `common/option.h:46`) — essentially a hand-written vtable:
```c
typedef struct FFModuleBaseInfo {
const char* name; // lookup key, e.g. "cpu"
const char* description; // help text
FFModuleDisplayName displayName; // module name in 20 languages
void (*initOptions)(void* options);
void (*destroyOptions)(void* options);
void (*parseJsonObject)(void* options, struct yyjson_val* module);
bool (*printModule)(void* options);
bool (*generateJsonResult)(void* options, yyjson_mut_doc*, yyjson_mut_val*);
void (*generateJsonConfig)(void* options, yyjson_mut_doc*, yyjson_mut_val*);
FFModuleFormatArgList formatArgs; // format placeholder table
const uint8_t defaultOrder; // sort weight
} FFModuleBaseInfo;
```
The source comment acknowledges that this is undefined behavior, since `void*` is not compatible with `FF*Options*`. It is a pragmatic compromise to obtain polymorphism in C; do not attempt to "fix" it.
### The registry: a first-letter hash bucket
`src/modules/modules.c` defines 26 `static FFModuleBaseInfo*` arrays (`A[]` through `Z[]`), each terminated by `nullptr`, collected into `ffModuleInfos[26]`:
```c
FFModuleBaseInfo** modules = ffModuleInfos[toupper(name[0]) - 'A']; // O(1) bucket lookup
for (; *modules; ++modules) { // linear scan in the bucket
if (ffStrEqualsIgnCase(name, (*modules)->name)) { ... }
}
```
Because the registry is small, a single arithmetic bucket lookup followed by a few string comparisons is preferable to a general-purpose hash table. When adding a module, place its descriptor in the bucket matching the first letter of `.name`, and keep the existing `nullptr` terminator at the end.
### The calling convention: zero heap allocation
Every dispatch site (`jsonconfig.c:98`, `commandoption.c:189`) follows the same pattern:
```c
alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; // 256 bytes, on the stack
baseInfo->initOptions(optionBuf);
baseInfo->parseJsonObject(optionBuf, jsonVal); // JSONC path only
baseInfo->printModule(optionBuf); // or generateJsonResult
baseInfo->destroyOptions(optionBuf);
```
`FF_OPTION_MAX_SIZE = 1 << 8` (256 bytes). Every module's `option.h` must end with:
```c
static_assert(sizeof(FFCPUOptions) <= FF_OPTION_MAX_SIZE, "FFCPUOptions size exceeds maximum allowed size");
```
**In other words, no module option struct may exceed 256 bytes.** This is a hard constraint, enforced at compile time.
The three dispatch entry points:
| Entry point | Location | Used for |
|---|---|---|
| `parseModuleJsonObject` | `common/impl/jsonconfig.c:90` | the `modules[]` array in a JSONC config |
| `parseStructureCommand` | `common/impl/commandoption.c:181` | the colon-separated structure string |
### Build-time module discovery
`CMakeLists.txt:134` globs `src/modules/*/*.c`, keeps only the entries whose file name matches their directory name, and generates a `MODULE_DISABLE_<UPPER>` option for each:
```cmake
file(GLOB FF_MODULE_SRCS CONFIGURE_DEPENDS RELATIVE "..." ".../src/modules/*/*.c")
set(FF_MODULE_DIRS "")
foreach(FF_MODULE_SRC ${FF_MODULE_SRCS})
get_filename_component(FF_MODULE_DIR "${FF_MODULE_SRC}" DIRECTORY)
get_filename_component(FF_MODULE_NAME "${FF_MODULE_SRC}" NAME_WE)
if("${FF_MODULE_DIR}" STREQUAL "${FF_MODULE_NAME}")
list(APPEND FF_MODULE_DIRS "${FF_MODULE_DIR}")
endif()
endforeach()
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
string(TOUPPER "${FF_MODULE_DIR}" FF_MODULE_UPPER)
option(MODULE_DISABLE_${FF_MODULE_UPPER} "Disable module ${FF_MODULE_DIR}" OFF)
endforeach()
```
Because the glob matches sources rather than directories, a directory counts as a module only when `src/modules/<name>/<name>.c` exists. Empty directories — which git does not track, so they are easy to leave behind — and stray files are ignored instead of breaking the configure step with `Cannot find source file`.
### Module sources are discovered automatically
`FF_MODULE_DIRS` also drives the source list (`CMakeLists.txt:516`):
```cmake
foreach(FF_MODULE_DIR ${FF_MODULE_DIRS})
list(APPEND LIBFASTFETCH_SRC
src/modules/${FF_MODULE_DIR}/${FF_MODULE_DIR}.c
)
endforeach()
```
Consequences:
- `src/modules/<name>/<name>.c` is compiled as soon as the file exists — **there is no source list to edit for the module layer**.
- The file name must match the directory name. A directory whose `.c` file is named differently, or has none at all, is silently not a module: a typo therefore shows up as a module missing from `fastfetch --list-modules`, not as a build error.
- The glob uses `CONFIGURE_DEPENDS`, so with the Makefile and Ninja generators the build re-evaluates it and re-runs CMake when a module source is added or removed. With other generators, or to be safe, re-run `cmake -B build` explicitly.
Only the module layer is automated. Sources under `src/detection/` and `src/common/impl/` are **not** globbed and must still be listed by hand — see [step 5](#5-add-the-platform-sources-to-cmakeliststxt).
---
## Walkthrough: adding a module
Adding a `Foo` module, step by step.
### 1. Define the option struct
`src/modules/foo/option.h`:
```c
#pragma once
#include "common/option.h"
typedef struct FFFooOptions {
FFModuleArgs moduleArgs; // must be the first field
bool showBar;
} FFFooOptions;
static_assert(sizeof(FFFooOptions) <= FF_OPTION_MAX_SIZE, "FFFooOptions size exceeds maximum allowed size");
```
`FFModuleArgs` must come first. It provides `key`, `format`, `outputColor`, `keyColor`, `keyIcon` and `keyWidth`; because it is the first field, `ffJsonConfigParseModuleArgs()` handles these generically, **so no parsing code is required**.
### 2. Implement detection
`src/detection/foo/foo.h`:
```c
#pragma once
#include "fastfetch.h"
typedef struct FFFooResult {
FFstrbuf name;
uint32_t count;
} FFFooResult;
const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result);
```
`src/detection/foo/foo_linux.c`:
```c
#include "foo.h"
const char* ffDetectFoo(const FFFooOptions* options, FFFooResult* result) {
// read /sys, /proc, sysfs, dbus, ...
return nullptr; // success; return an error string on failure
}
```
**Write one file per target platform, plus a `foo_nosupport.c` fallback for the remaining platforms.** The repository currently contains 45 `*_nosupport.c` files serving this purpose.
### 3. Implement the module
`src/modules/foo/foo.c` — implement the six functions, then define the descriptor:
```c
FFModuleBaseInfo ffFooModuleInfo = {
.name = "Foo",
.description = "Print foo information",
.displayName = {
.en = "Foo", .ar = "Foo", .cs = "Foo", .de = "Foo", .es = "Foo",
.fr = "Foo", .gl = "Foo", .he = "Foo", .id = "Foo", .it = "Foo",
.ja = "Foo", .ko = "Foo", .pl = "Foo", .pt = "Foo", .ru = "Foo",
.tr = "Foo", .uk = "Foo", .vi = "Foo", .zh_CN = "Foo", .zh_TW = "Foo",
},
.initOptions = (void*) ffInitFooOptions,
.destroyOptions = (void*) ffDestroyFooOptions,
.parseJsonObject = (void*) ffParseFooJsonObject,
.printModule = (void*) ffPrintFoo,
.generateJsonResult = (void*) ffGenerateFooJsonResult,
.generateJsonConfig = (void*) ffGenerateFooJsonConfig,
.formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) {
{ "Name", "name" },
{ "Count", "count" },
})),
.defaultOrder = 73,
};
```
Points to note:
- **`displayName`** is a literal block of all 20 language fields; there is no shortcut macro, so copy the layout from an existing module: **`en`, `ar`, `cs`, `de`, `es`, `fr`, `gl`, `he`, `id`, `it`, `ja`, `ko`, `pl`, `pt`, `ru`, `tr`, `uk`, `vi`, `zh_CN`, `zh_TW`**.
**Fill in all 20.** The struct is read by byte offset (see [Pitfalls](#6-localization-uses-byte-offsets-not-enums)) and there is **no fallback** — a missing field means the key prints empty in that language.
- **`formatArgs`** must be exhaustive. It drives the placeholder list printed by `fastfetch -h foo-format`; **any placeholder omitted here is neither listed nor usable by the user.**
- The `moduleFormat` section in `doc/json_schema.json` is generated by `fastfetch -h format-json`. Its metadata comes from `FFModuleBaseInfo::formatArgs`. To keep the schema and module descriptors consistent, do not edit the `moduleFormat` section in `doc/json_schema.json` by hand; update the module metadata and regenerate it instead.
- **`defaultOrder`**: use the current maximum plus 1. Search the existing descriptors for `.defaultOrder =` before choosing a value; do not copy a hard-coded value from this document. Leaving it out, or setting it to `0`, excludes the module from the interactive `--gen-config` picker. Only `logo`, `command` and `custom` rely on this behavior, because they require user arguments or are invoked directly by the display layer.
### 4. Register it
- Add `#include "modules/foo/foo.h"` to `src/modules/modules.h`
- Insert into the `F[]` array in `src/modules/modules.c`:
```c
#if !FF_MODULE_DISABLE_FOO
&ffFooModuleInfo,
#endif
```
`FF_MODULE_DISABLE_FOO` is generated by CMake — you do not define it yourself.
### 5. Add the platform sources to `CMakeLists.txt`
The module layer is discovered by glob (see [Build-time module discovery](#module-sources-are-discovered-automatically)), **but `src/detection/**` and `src/common/impl/**` are not**. `CMakeLists.txt` lists them explicitly, inside one mutually exclusive chain of platform blocks:
| Block | Line | Covers |
|---|---|---|
| `if(LINUX)` | `CMakeLists.txt:522` | Linux |
| `elseif(ANDROID)` | `CMakeLists.txt:608` | Android (Termux) |
| `elseif(FreeBSD)` | `CMakeLists.txt:691` | FreeBSD, MidnightBSD, DragonFly |
| `elseif(NetBSD)` | `CMakeLists.txt:788` | NetBSD |
| `elseif(OpenBSD)` | `CMakeLists.txt:872` | OpenBSD |
| `elseif(APPLE)` | `CMakeLists.txt:959` | macOS / iOS |
| `elseif(WIN32)` | `CMakeLists.txt:1046` | Windows |
| `elseif(SunOS)` | `CMakeLists.txt:1121` | Solaris / illumos |
| `elseif(Haiku)` | `CMakeLists.txt:1204` | Haiku |
| `elseif(GNU)` | `CMakeLists.txt:1282` | GNU/Hurd |
Add the platform implementation to every block whose platform it supports, and `foo_nosupport.c` to every remaining block, so that all ten platforms still link:
```cmake
elseif(FreeBSD)
list(APPEND LIBFASTFETCH_SRC
...
src/detection/foo/foo_bsd.c
)
```
Points to note:
- Exactly one block is compiled per build, so a file omitted from a block does not exist for that platform, and the link fails with an undefined reference to `ffDetectFoo()` — **on that platform only**. A missing entry therefore builds fine locally and fails in CI; this is why every block must be covered.
- A platform may reuse another platform's implementation instead of a stub. `src/common/impl/networking_linux.c`, for example, is listed in nine of the ten blocks (all but `WIN32`). Check where the closest sibling module points before adding a new file.
- `DragonFly` is handled by the inner `if(DragonFly)` sub-block inside the FreeBSD block (`CMakeLists.txt:773`); add the variant there, as `processes`, `top` and `wifi` do.
- New helpers under `src/common/impl/` follow the same rule: they are not globbed, and each block that needs one must list it.
- A few files are appended outside the platform chain because they depend on an option or on a specific feature — for example the proprietary GPU backends (`CMakeLists.txt:1379`) and `src/common/impl/wcwidth.c` (`CMakeLists.txt:1408`). Those are written by hand as well.
### 6. Modules that need warm-up
If your module needs a sampling interval (CPU usage) or a network round-trip (public IP, weather), also implement `ffPrepareFoo()` and register it in the switch inside `ffPrepareCommandOption()` in `common/impl/commandoption.c`, under the matching first-letter `case`. Six modules currently do this: CPUUsage, DiskIO, NetIO, PublicIP, Top and Weather.
### 7. Verify
```sh
cmake -B build && cmake --build build -j
./build/fastfetch -s foo --format json # JSON output
./build/fastfetch -h foo-format # list formatArgs placeholders
./build/fastfetch --gen-config # confirm it appears in the picker
```
Note the `-format` suffix on the help flag: `fastfetch -h foo` is not supported; only `fastfetch -h foo-format` works.
If you added detection sources, re-check [step 5](#5-add-the-platform-sources-to-cmakeliststxt) before pushing: a platform block you missed compiles fine locally and fails only when that platform is built.
---
## Walkthrough: adding a logo
A new logo **must** have a corresponding "Logo Request" issue, linked from the PR with `Closes #1234`. Logo PRs without a linked issue are not accepted.
### 1. Add the ASCII file
```
src/logo/ascii/<first-letter>/<distro>.txt
```
For example `src/logo/ascii/d/distro.txt`. Directories are already split by first letter (`a/` … `z/`, plus `_/`).
The file is plain ASCII art with `$1` … `$9` as color placeholders:
```
$3 ,-^-___
$3 /\\\///
$2refined.$1 /\\\\//
```
- `$1` … `$9` map to palette slots 19; at most 9 are supported (`FASTFETCH_LOGO_MAX_COLORS = 9`)
- `$$` is a literal `$`
- Characters without a placeholder inherit the current color. Of the 530 existing logos, 239 use no placeholders at all (monochrome) and 291 do; **new logos should use placeholders**, as monochrome is a legacy style
- Tabs are expanded to 4 spaces
- Colors can be overridden with `--logo-color-1` … `--logo-color-9`
### 2. Register it in the `.inc`
CMake turns each `.txt` file into a `FASTFETCH_DATATEXT_LOGO_<UPPERCASE>` macro (`CMakeLists.txt:418`), but **the registry itself is maintained by hand**. Edit `src/logo/ascii/<first-letter>.inc`:
```c
// src/logo/ascii/d.inc
#ifdef FASTFETCH_DATATEXT_LOGO_DISTRO
// Distro
{
.names = { "Distro" }, // ID (preferred) or NAME from /etc/os-release, do NOT add both
.lines = FASTFETCH_DATATEXT_LOGO_DISTRO,
.colors = {
FF_COLOR_FG_PRIMARY, // recommended for using as WHITE replacement, light-theme terminal friendly
FF_COLOR_FG_BLUE, // preferred
FF_COLOR_FG_256 "34",
FF_COLOR_FG_RGB "0;255;0", // not recommended because of bad compatibility with raw TTY
},
},
#endif
```
- `names` is matched case-insensitively. Do not add names that differ only by case, because they can never be distinguished. Every name is also shown by `--list-logos`, so use user-friendly spelling such as an initial capital where appropriate.
- For a new logo, use exactly one name unless there is a specific compatibility reason to add more. That name should first be the `ID` from `/etc/os-release`. If the ID cannot distinguish this logo from another logo for the same OS, use the `NAME` value instead. `logoGetBuiltinDetected` in `src/logo/logo.c` documents the detection order: `ID`, then `NAME`, then tokens from `ID_LIKE`, then the platform name fallback. Manual `logo-source` selection uses the name directly.
- `colors` is positional — entry 0 is `$1`, entry 1 is `$2`, and so on
- `colors[0]` also becomes the title color and `colors[1]` the key color, unless the user overrides them
If the same OS has multiple logo variants, mark the variant explicitly with `.type`:
```c
.type = FF_LOGO_LINE_TYPE_SMALL_BIT,
```
Use `FF_LOGO_LINE_TYPE_ALTER_BIT` for an alternate logo, `FF_LOGO_LINE_TYPE_SMALL_BIT` for a small logo, or combine the flags when both apply. This is also a lookup optimization: a logo marked `FF_LOGO_LINE_TYPE_SMALL_BIT` is considered only for `type = small`, while a logo marked `FF_LOGO_LINE_TYPE_ALTER_BIT` is never selected by automatic detection. Alternate logos are available only when explicitly requested through `-l <source>`.
### 3. Verify
```sh
./build/fastfetch -l distro
./build/fastfetch --list-logos | grep -i distro
```
---
## Platform conventions
The `detection/` layer picks its implementation by **filename suffix**; the choice is made at build time from `CMAKE_SYSTEM_NAME`:
| Suffix | Platform |
|---|---|
| `_linux.c` | Linux |
| `_apple.c` / `_apple.m` | macOS / iOS (`.m` for Objective-C) |
| `_windows.c` / `_windows.cpp` | Windows (`.cpp` for WinRT / WMI) |
| `_bsd.c` | FreeBSD |
| `_nbsd.c` | NetBSD |
| `_obsd.c` | OpenBSD |
| `_sunos.c` | Solaris / illumos |
| `_haiku.c` / `.cpp` | Haiku |
| `_android.c` | Android (Termux) |
| `_gnu.c` | GNU/Hurd |
| `_nosupport.c` | Empty fallback |
Current distribution of platform files in `detection/`:
| Suffix | Files | Suffix | Files |
|---|---|---|---|
| `_linux` | 58 | `_obsd` | 16 |
| `_windows` | 47 | `_haiku` | 13 |
| `_apple` | 46 | `_android` | 12 |
| `_nosupport` | 45 | `_gnu` | 3 |
| `_bsd` | 29 | | |
| `_sunos` | 19 | | |
| `_nbsd` | 17 | | |
(These are repository inventory figures, counting `.c`, `.m`, `.cpp`, and `.h`; they may change as platforms and modules are added.)
**Do not accumulate `#ifdef __linux__` blocks in a single file.** When adding platform support, copy the closest existing implementation and change the suffix.
`common/` follows the same convention: `common/impl/` contains `io_unix.c` / `io_windows.c`, `netif_linux.c` / `netif_apple.c` / `netif_bsd.c` and similar files, with `common/apple/`, `common/windows/` and `common/haiku/` holding platform-specific helpers.
When several platform suffixes could apply, use the most specific implementation supported by the build system (for example, `_nbsd.c` instead of the generic `_bsd.c` on NetBSD). Keep the generic file as the fallback for platforms that share its conventions.
---
## Code style
### Formatting
The project uses clang-format with `BasedOnStyle: LLVM` plus these key overrides:
```yaml
IndentWidth: 4
UseTab: Never
ColumnLimit: 0 # never wrap
InsertBraces: true # braces even on single-statement ifs
PointerAlignment: Left # char* p, not char *p
SortIncludes: Never # include order is managed by hand
AlignAfterOpenBracket: DontAlign
BinPackParameters: false # all params on one line, or one per line
```
Before committing:
```sh
clang-format -i src/modules/foo/*.c src/modules/foo/*.h
```
`src/3rdparty/**`, `build/**` and `src/logo/builtin.c` are listed in `.clang-format-ignore` — **do not reformat them.**
`.editorconfig` specifies LF line endings, a 4-space indent, a final newline, and trailing whitespace trimmed (except in Markdown).
### Naming
| Kind | Convention | Example |
|---|---|---|
| Functions | `ff` + PascalCase | `ffDetectCPU`, `ffPrintCPU` |
| Types | `FF` + PascalCase | `FFCPUResult`, `FFModuleBaseInfo` |
| Variables / fields | camelCase | `coresPhysical`, `keyWidth` |
| Macros | SCREAMING_SNAKE_CASE | `FF_OPTION_MAX_SIZE` |
| Module options | `FF<Name>Options` | `FFCPUOptions` |
| Detection results | `FF<Name>Result` | `FFCPUResult` |
### Spelling
CI runs codespell (`.codespellrc`). Known false positives are listed in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code.
### Compiler warnings
The build enables `-Wall -Wextra -Wconversion` plus several `-Werror`s:
```
-Werror=uninitialized -Werror=return-type -Werror=vla
-Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion
```
**`-Wconversion` is strict**: every implicit narrowing conversion needs an explicit cast.
---
## Commit messages
Format:
```
<Scope>[ (Platform)]: <third-person singular verb> <object>
```
Scope is the module or subsystem name; Platform is optional. Use the third-person singular present tense, capitalize the first letter, no trailing period.
Real examples from recent history:
```
Processes (Haiku): honors `options->countKprocs`
WM (macOS): improves reliability of WM plugin detection
Memory (Windows): prefers `NQSI`
Top (Linux): improves performance of `stat` parsing
Logo (Builtin): adds omarchy
Global: introduces global macro `FF_PATH_PKG_BASE` to replace `_PATH_LOCALBASE`
LM (OpenBSD): adds support
CI: disables fail on alert
Doc: updates README
Presets: moves `top` to the bottom of running modules [ci skip]
```
Common verbs: `adds`, `removes`, `fixes`, `improves`, `updates`, `corrects`, `prefers`, `honors`, `disables`, `enables`, `introduces`, `detects`, `reports`, `skips`, `uses`.
Scopes in use: `Top`, `Processes`, `Memory`, `Logo (Builtin)`, `CI`, `Doc`, `Presets`, `Global`, plus individual module names.
Documentation-only changes use a `[ci skip]` suffix.
---
## Changelog
User-visible changes go into `CHANGELOG.md`, under the topmost version heading:
```markdown
# Unreleased
Changes:
* The DE / WM / LM modules now reports the full name ...
Features:
* Added Top module to print processes with the highest CPU, memory or disk I/O usage. (Top)
* Improved Wi-Fi module
* Added Wifi channel width detection, exposed via `{channel-width}` in custom format.
* Improved Wifi channel frequency accuracy on Windows, macOS.
* Added Battery detection support on SunOS. (Battery, SunOS)
Bugfixes:
* Fixed I/O rate calculation precision in DiskIO and NetIO. (DiskIO / NetIO)
```
Rules:
- Three sections: `Changes:` (behavior changes), `Features:`, `Bugfixes:`
- Past tense: `Added` / `Fixed` / `Improved`
- End each entry with its scope: `(Module, Platform)` or `(ModuleA / ModuleB)`
- Sub-bullets are indented 4 spaces
---
## Tests
Tests are standalone executables in `tests/` using a `VERIFY` macro that exits non-zero on failure:
```c
#define VERIFY(expression) \
if (!(expression)) testFailed(&strbuf, #expression, __LINE__)
```
Current tests: `strbuf.c`, `list.c`, `format.c`, `color.c`, `duration.c`, `strutil.c`.
```sh
cmake -B build -DBUILD_TESTS=On
cmake --build build
cd build && ctest --output-on-failure
```
Coverage focuses on the core data structures and the formatting engine in `common/`. The `detection/` layer has no automated tests, because it depends on the state of a running system; it is instead covered by the CI matrix — 20 workflows under `.github/workflows/` spanning Linux (including musl, loong64, armv7l, i686), macOS, Windows, FreeBSD, NetBSD, OpenBSD, DragonFly, Solaris, OmniOS and Haiku, plus spellcheck and benchmark jobs.
**If you modify `common/FFstrbuf.h`, `common/format.h` or `common/color.h`, extend the corresponding test.**
---
## Pull requests
1. **Open an issue first** (feature request / bug report / logo request) to confirm that the change is wanted before investing effort. Templates are in `.github/ISSUE_TEMPLATE/`.
2. Branch off `dev` — **`dev` is the main development branch**, not `master`.
3. Follow the [commit message convention](#commit-messages).
4. Update `CHANGELOG.md` for user-visible changes.
5. Open the PR against `dev` and fill in `.github/pull_request_template.md`:
- Summary
- Related issue (**required for new logos**; otherwise the PR is not accepted)
- Changes
- Screenshots (required for visual changes)
- Checklist: confirm you tested locally
### Pre-submit checklist
```sh
clang-format -i <changed files> # format
codespell # spelling
cmake -B build -DBUILD_TESTS=On && cmake --build build -j
cd build && ctest --output-on-failure # tests
./build/fastfetch --format json # verify JSON output is well-formed
./build/fastfetch -c presets/all.jsonc --stat false # smoke-test every module
```
---
## Pitfalls
The following points are easy to misinterpret when reading this codebase. Most of them follow from the startup-time objective described above.
### 1. Option structs must not exceed 256 bytes
`FF_OPTION_MAX_SIZE = 1 << 8`. Exceeding it is caught at compile time by `static_assert`. Do not attempt to increase the value: it determines the stack cost of every module invocation.
### 2. `FF_MODULE_DISABLE_*` controls registration, not compilation
```c
// verbatim from the top of modules/modules.c:
// FF_MODULE_DISABLE_<module> only controls if the module is registered,
// the module code itself is still compiled.
// We rely `LTO` to remove the unused code (only enabled in Release mode)
```
**Disabling modules in a Debug build does not shrink the binary.** LTO is enabled whenever `CMAKE_BUILD_TYPE != Debug`, and the default `RelWithDebInfo` already satisfies that — so measure size with `RelWithDebInfo` or `Release`, never with `Debug`. (The "Release mode" wording in the comment above is imprecise.)
### 3. Only two things are discovered by glob
The module sources (`CMakeLists.txt:134`) and the logo `.txt` files (`CMakeLists.txt:429`). Both use `CONFIGURE_DEPENDS`, so with the Makefile and Ninja generators the build re-checks the glob and re-runs CMake when the result changes; other generators (Visual Studio and Xcode in particular) do not track it as reliably. Re-run `cmake -B build` after adding a module source or a logo file rather than relying on that behavior.
Everything else — `src/detection/**` and `src/common/impl/**`, and any source outside the platform chain — must be listed in `CMakeLists.txt` by hand; see [step 5](#5-add-the-platform-sources-to-cmakeliststxt).
### 4. CLI module options are removed
Per-module command-line flags such as `--cpu-temp` are **no longer supported**. The only job of `ffParseModuleOptions` now is to translate the flag into a JSON key, then `exit(477)` with a pointer to the config file:
```
Error: Unsupported module option: --cpu-temp
Support of module options has been removed. Please add the flag to the JSON config instead.
Example (demonstration only): `{ "modules": [ { "type": "cpu", "temp": true } ] }`
```
**JSONC is the first-class configuration interface; the command line is not.** When adding module options, implement `parseJsonObject` only — do not add a CLI branch.
### 5. `defaultOrder = 0` hides a module from `--gen-config`
`collectModuleInfos` (`genconfig.c:186`) skips any module whose `defaultOrder` is `0`. C zero-initializes the field, so **omitting it is the same as setting it to 0**. Only `logo`, `command` and `custom` rely on this intentionally.
`defaultOrder` affects only the ordering in the interactive `--gen-config` picker; it has no effect on the runtime output order.
### 6. Localization uses byte offsets, not enums
`instance.config.display.keyLanguage` does not hold a language enum — it holds a **byte offset** such as `offsetof(FFModuleDisplayName, zh_CN)`. The macro `FF_MODULE_GET_DISPLAY_NAME` (`common/option.h:123`) does plain pointer arithmetic with it:
```c
#define FF_MODULE_GET_DISPLAY_NAME(moduleName) \
(*(const char**) ((uint8_t*) &ff ## moduleName ## ModuleInfo.displayName + instance.config.display.keyLanguage))
```
Consequence: **the field order of `FFModuleDisplayName` must never change** — reordering the fields silently corrupts the output for every language.
### 7. The `multithreading` option has limited effect
The global `multithreading` option currently takes effect in exactly one place: `common/impl/networking_linux.c:339`. Modules are still printed sequentially. Modules that need concurrency go through the `ffPrepare*` warm-up hooks instead, which start sampling or fire off requests before the print loop begins.
This may change in the future. The main blocking issue is that there are dependencies between different modules.
### 8. Do not print or read configuration inside `detection/`
The `detection/` layer must stay pure: read system state, fill a struct, return an error string. Any `printf` or any read of `instance.config` there is a design error.
Use `FF_DEBUG` (`src/common/debug.h`) for logging.
### 9. `src/logo/builtin.c` and `3rdparty/` are formatting-exempt
The former is a large generated and hand-maintained data table; the latter is upstream code. Both are excluded via `.clang-format-ignore`; do not reformat either.
---
## Reference
- Configuration guide: <https://github.com/fastfetch-cli/fastfetch/wiki/Configuration>
- JSON schema: `doc/json_schema.json`
- Example presets: `presets/` (`all.jsonc`, `neofetch.jsonc`, `ci.jsonc`, …)
- Man page source: `doc/fastfetch.1.in`
- Code generation scripts: `scripts/` (`gen-pciids.py`, `gen-amdgpuids.py`, `gen-man.py`)
+6 -5
View File
@@ -1,5 +1,6 @@
# Fastfetch
[![Benchmark](https://img.shields.io/badge/GitHub%20Pages-live-blue?logo=github)](https://fastfetch-cli.github.io/fastfetch/dev/bench)
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/fastfetch-cli/fastfetch/ci.yml)](https://github.com/fastfetch-cli/fastfetch/actions)
[![GitHub license](https://img.shields.io/github/license/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/blob/dev/LICENSE)
[![GitHub contributors](https://img.shields.io/github/contributors/fastfetch-cli/fastfetch)](https://github.com/fastfetch-cli/fastfetch/graphs/contributors)
@@ -99,7 +100,7 @@ See the Wiki: https://github.com/fastfetch-cli/fastfetch/wiki/Building
* View all data that fastfetch detects: `fastfetch -s <module1>[:<module2>][:<module3>] --format json`
* Display help messages: `fastfetch --help`
* Generate a minimal config file: `fastfetch [-s <module1>[:<module2>]] --gen-config [</path/to/config.jsonc>]`
* Use: `--gen-config-full` to generate a full config file with all optional options
* Use [The online configuration generator](https://fastfetch-cli.github.io/fastfetch-config/) to generate a full config file with all optional options
## Customization
@@ -335,10 +336,10 @@ If you find Fastfetch useful, please consider donating.
Give us a star to show your support!
<a href="https://star-history.com/#fastfetch-cli/fastfetch&Date">
<a href="https://star-history.dera.page/#fastfetch-cli/fastfetch&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=fastfetch-cli/fastfetch&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=fastfetch-cli/fastfetch&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=fastfetch-cli/fastfetch&type=Date" />
<source media="(prefers-color-scheme: dark)" srcset="https://star-history.dera.page/svg?repos=fastfetch-cli/fastfetch&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://star-history.dera.page/svg?repos=fastfetch-cli/fastfetch&type=Date" />
<img alt="Star History Chart" src="https://star-history.dera.page/svg?repos=fastfetch-cli/fastfetch&type=Date" />
</picture>
</a>
+56 -24
View File
@@ -63,46 +63,78 @@ echo '
import json, subprocess, sys
def escape_fish_dq(s: str) -> str:
"""Escape special characters for Fish double quotes to prevent unexpected expansions. One slash in output needs four slashes in script"""
return str(s).replace("\\\\", "\\\\\\\\").replace("\\"", "\\\\\\"").replace("$", "\\\\$")
def main():
data: dict[str, list[dict]] = json.loads(subprocess.check_output(["fastfetch", "--help-raw"]))
enum_functions = []
complete_commands = []
for key in data:
for flag in data[key]:
if flag["long"] == "logo-color-[1-9]":
long_name = flag.get("long", "")
# Handle pseudo flag logo-color-[1-9]
if long_name == "logo-color-[1-9]":
safe_desc_global = escape_fish_dq(flag.get("desc", ""))
for i in range(1, 10):
print(f"""complete -c fastfetch -d "{flag["desc"]}" -l "logo-color-{i}" -x -a "(__fastfetch_complete_color)" """)
complete_commands.append(f"complete -c fastfetch -d \"{safe_desc_global}\" -l \"logo-color-{i}\" -x -a \"(__fastfetch_complete_color)\"")
continue
if flag.get("pseudo", False):
continue
command_prefix = f"""complete -c fastfetch -d "{flag["desc"]}" -l "{flag["long"]}\""""
safe_desc_global = escape_fish_dq(flag.get("desc", ""))
command_prefix = f"complete -c fastfetch -d \"{safe_desc_global}\" -l \"{long_name}\""
if "short" in flag:
command_prefix += f""" -o {flag["short"]}"""
short_name = flag["short"]
command_prefix += f" -o {short_name}"
if "arg" in flag:
type: str = flag["arg"]["type"]
if type == "bool":
print(f"{command_prefix} -x -a \"(__fastfetch_complete_bool)\"")
elif type == "color":
print(f"{command_prefix} -x -a \"(__fastfetch_complete_color)\"")
elif type == "command":
print(f"{command_prefix} -x -a \"(__fastfetch_complete_command)\"")
elif type == "config":
print(f"{command_prefix} -x -a \"(__fastfetch_complete_config)\"")
elif type == "enum":
temp: str = " ".join(flag["arg"]["enum"])
print(f"{command_prefix} -x -a \"{temp}\"")
elif type == "logo":
print(f"{command_prefix} -x -a \"(__fastfetch_complete_logo)\"")
elif type == "structure":
print(f"{command_prefix} -x -a \"(__fish_complete_list : __fastfetch_complete_structure)\"")
elif type == "path":
print(f"{command_prefix} -r -F")
arg_type: str = flag["arg"]["type"]
if arg_type == "bool":
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_bool)\"")
elif arg_type == "color":
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_color)\"")
elif arg_type == "command":
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_command)\"")
elif arg_type == "config":
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_config)\"")
elif arg_type == "enum":
# Sanitize flag name to create a valid Fish function name
func_name = long_name.replace("-", "_").replace("[", "_").replace("]", "_")
func_name = f"__fastfetch_complete_enum_{func_name}"
# Generate the enum function
func_lines = [f"function {func_name}"]
for enum_val, enum_desc in flag["arg"]["enum"].items():
safe_val = escape_fish_dq(enum_val)
safe_desc = escape_fish_dq(enum_desc)
func_lines.append(f" echo -e \"{safe_val}\\t{safe_desc}\"")
func_lines.append("end\n")
enum_functions.append("\n".join(func_lines))
# Append complete command pointing to the new function
complete_commands.append(f"{command_prefix} -x -a \"({func_name})\"")
elif arg_type == "logo":
complete_commands.append(f"{command_prefix} -x -a \"(__fastfetch_complete_logo)\"")
elif arg_type == "structure":
complete_commands.append(f"{command_prefix} -x -a \"(__fish_complete_list : __fastfetch_complete_structure)\"")
elif arg_type == "path":
complete_commands.append(f"{command_prefix} -r -F")
else:
print(f"{command_prefix} -x")
complete_commands.append(f"{command_prefix} -x")
else:
print(f"{command_prefix} -f")
complete_commands.append(f"{command_prefix} -f")
# Print generated enum functions first, then complete rules
print("\n".join(enum_functions))
print("\n".join(complete_commands))
if __name__ == "__main__":
+18
View File
@@ -1,3 +1,21 @@
fastfetch (2.68.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.68.1
-- Carter Li <zhangsongcui@live.cn> Wed, 02 Sep 2026 10:47:51 +0800
fastfetch (2.68.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.68.0
-- Carter Li <zhangsongcui@live.cn> Tue, 01 Sep 2026 16:34:36 +0800
fastfetch (2.67.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.67.0
-- Carter Li <zhangsongcui@live.cn> Thu, 06 Aug 2026 16:22:22 +0800
fastfetch (2.66.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.66.0
+32
View File
@@ -585,6 +585,38 @@
"default": "string"
}
},
{
"long": "key-language",
"desc": "Specify the language used for module keys",
"remark": "Leave empty for auto-detection based on the system locale. Unsupported locales fall back to English",
"arg": {
"type": "enum",
"default": "en",
"optional": true,
"enum": {
"en": "English",
"ar": "Arabic",
"cs": "Czech",
"de": "German",
"es": "Spanish",
"fr": "French",
"gl": "Galician",
"he": "Hebrew",
"id": "Indonesian",
"it": "Italian",
"ja": "Japanese",
"ko": "Korean",
"pl": "Polish",
"pt": "Portuguese",
"ru": "Russian",
"tr": "Turkish",
"uk": "Ukrainian",
"vi": "Vietnamese",
"zh_CN": "Simplified Chinese",
"zh_TW": "Traditional Chinese"
}
}
},
{
"long": "bright-color",
"desc": "Specify whether keys, title, and ASCII logo should be printed in bright color",
+226 -8
View File
@@ -398,7 +398,7 @@
"type": "string"
},
"lmFormat": {
"description": "Output format for the `LM` module. See Wiki for formatting syntax\n 1. {service}: LM service\n 2. {type}: LM type\n 3. {version}: LM version",
"description": "Output format for the `LM` module. See Wiki for formatting syntax\n 1. {service}: LM service / process name\n 2. {pretty-name}: LM pretty name\n 3. {version}: LM version",
"type": "string"
},
"loadavgFormat": {
@@ -446,7 +446,7 @@
"type": "string"
},
"packagesFormat": {
"description": "Output format for the `Packages` module. See Wiki for formatting syntax\n 1. {am-system}: Number of am-system packages\n 2. {am-user}: Number of am-user (aka appman) packages\n 3. {appimage}: Number of appimage packages\n 4. {apk}: Number of apk packages\n 5. {brew}: Number of brew packages\n 6. {brew-cask}: Number of brew-cask packages\n 7. {cards}: Number of cards packages\n 8. {choco}: Number of choco packages\n 9. {dpkg}: Number of dpkg packages\n 10. {emerge}: Number of emerge packages\n 11. {eopkg}: Number of eopkg packages\n 12. {flatpak-system}: Number of flatpak-system app packages\n 13. {flatpak-user}: Number of flatpak-user app packages\n 14. {guix-home}: Number of guix-home packages\n 15. {guix-system}: Number of guix-system packages\n 16. {guix-user}: Number of guix-user packages\n 17. {hpkg-system}: Number of hpkg-system packages\n 18. {hpkg-user}: Number of hpkg-user packages\n 19. {install-release}: Number of install-release packages\n 20. {kiss}: Number of kiss packages\n 21. {linglong}: Number of linglong packages\n 22. {lpkg}: Number of lpkg packages\n 23. {lpkgbuild}: Number of lpkgbuild packages\n 24. {macports}: Number of macports packages\n 25. {mport}: Number of mport packages\n 26. {moss}: Number of moss packages\n 27. {nix-default}: Number of nix-default packages\n 28. {nix-system}: Number of nix-system packages\n 29. {nix-user}: Number of nix-user packages\n 30. {opkg}: Number of opkg packages\n 31. {pacman}: Number of pacman packages\n 32. {pacman-branch}: Pacman branch on manjaro\n 33. {pacstall}: Number of pacstall packages\n 34. {paludis}: Number of paludis packages\n 35. {pisi}: Number of pisi packages\n 36. {pkg}: Number of pkg packages\n 37. {pkgsrc}: Number of pkgsrc packages\n 38. {pkgtool}: Number of pkgtool packages\n 39. {porg}: Number of porg packages\n 40. {rpm}: Number of rpm packages\n 41. {scoop-global}: Number of scoop-global packages\n 42. {scoop-user}: Number of scoop-user packages\n 43. {snap}: Number of snap packages\n 44. {soar}: Number of soar packages\n 45. {sorcery}: Number of sorcery packages\n 46. {winget}: Number of winget packages\n 47. {xbps}: Number of xbps packages\n 48. {brew-all}: Total number of all brew packages\n 49. {flatpak-all}: Total number of all flatpak app packages\n 50. {guix-all}: Total number of all guix packages\n 51. {hpkg-all}: Total number of all hpkg packages\n 52. {nix-all}: Total number of all nix packages\n 53. {all}: Number of all packages",
"description": "Output format for the `Packages` module. See Wiki for formatting syntax\n 1. {am-system}: Number of am-system packages\n 2. {am-user}: Number of am-user (aka appman) packages\n 3. {appimage}: Number of appimage packages\n 4. {apk}: Number of apk packages\n 5. {brew}: Number of brew packages\n 6. {brew-cask}: Number of brew-cask packages\n 7. {cards}: Number of cards packages\n 8. {choco}: Number of choco packages\n 9. {crux}: Number of crux packages\n 10. {dpkg}: Number of dpkg packages\n 11. {emerge}: Number of emerge packages\n 12. {eopkg}: Number of eopkg packages\n 13. {flatpak-system}: Number of flatpak-system app packages\n 14. {flatpak-user}: Number of flatpak-user app packages\n 15. {guix-home}: Number of guix-home packages\n 16. {guix-system}: Number of guix-system packages\n 17. {guix-user}: Number of guix-user packages\n 18. {hpkg-system}: Number of hpkg-system packages\n 19. {hpkg-user}: Number of hpkg-user packages\n 20. {install-release}: Number of install-release packages\n 21. {kiss}: Number of kiss packages\n 22. {linglong}: Number of linglong packages\n 23. {lpkg}: Number of lpkg packages\n 24. {lpkgbuild}: Number of lpkgbuild packages\n 25. {macports}: Number of macports packages\n 26. {mport}: Number of mport packages\n 27. {moss}: Number of moss packages\n 28. {nix-default}: Number of nix-default packages\n 29. {nix-system}: Number of nix-system packages\n 30. {nix-user}: Number of nix-user packages\n 31. {opkg}: Number of opkg packages\n 32. {pacman}: Number of pacman packages\n 33. {pacman-branch}: Pacman branch on manjaro\n 34. {pacstall}: Number of pacstall packages\n 35. {paludis}: Number of paludis packages\n 36. {pisi}: Number of pisi packages\n 37. {pkg}: Number of pkg packages\n 38. {pkgsrc}: Number of pkgsrc packages\n 39. {pkgtool}: Number of pkgtool packages\n 40. {porg}: Number of porg packages\n 41. {rpm}: Number of rpm packages\n 42. {scoop-global}: Number of scoop-global packages\n 43. {scoop-user}: Number of scoop-user packages\n 44. {snap}: Number of snap packages\n 45. {soar}: Number of soar packages\n 46. {sorcery}: Number of sorcery packages\n 47. {winget}: Number of winget packages\n 48. {xbps}: Number of xbps packages\n 49. {brew-all}: Total number of all brew packages\n 50. {flatpak-all}: Total number of all flatpak app packages\n 51. {guix-all}: Total number of all guix packages\n 52. {hpkg-all}: Total number of all hpkg packages\n 53. {nix-all}: Total number of all nix packages\n 54. {all}: Number of all packages",
"type": "string"
},
"physicaldiskFormat": {
@@ -505,6 +505,10 @@
"description": "Output format for the `Title` module. See Wiki for formatting syntax\n 1. {user-name}: User name\n 2. {host-name}: Host name\n 3. {home-dir}: Home directory\n 4. {exe-path}: Executable path of current process\n 5. {user-shell}: User's default shell\n 6. {user-name-colored}: User name (colored)\n 7. {at-symbol-colored}: @ symbol (colored)\n 8. {host-name-colored}: Host name (colored)\n 9. {full-user-name}: Full user name\n 10. {user-id}: UID (*nix) / SID (Windows)\n 11. {pid}: PID of current process\n 12. {cwd}: CWD with home dir replaced by `~`",
"type": "string"
},
"topFormat": {
"description": "Output format for the `Top` module. See Wiki for formatting syntax\n 1. {name}: Process name\n 2. {pid}: Process ID\n 3. {cpu}: CPU usage\n 4. {mem}: Memory usage (RSS) in bytes\n 5. {disk-read}: Disk read bytes per second (0 if unsupported)\n 6. {disk-write}: Disk write bytes per second (0 if unsupported)\n 7. {threads}: Number of threads\n 8. {cpu-percentage}: CPU usage percentage\n 9. {mem-formatted}: Memory usage (RSS) formatted\n 10. {disk-read-formatted}: Disk read formatted\n 11. {disk-write-formatted}: Disk write formatted",
"type": "string"
},
"themeFormat": {
"description": "Output format for the `Theme` module. See Wiki for formatting syntax\n 1. {theme1}: Theme part 1\n 2. {theme2}: Theme part 2",
"type": "string"
@@ -542,7 +546,7 @@
"type": "string"
},
"wifiFormat": {
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {band}: Connection channel band in GHz",
"description": "Output format for the `Wifi` module. See Wiki for formatting syntax\n 1. {inf-desc}: Interface description\n 2. {inf-status}: Interface status\n 3. {status}: Connection status\n 4. {ssid}: Connection SSID\n 5. {bssid}: Connection BSSID\n 6. {protocol}: Connection protocol\n 7. {signal-quality}: Connection signal quality (percentage num)\n 8. {rx-rate}: Connection RX rate\n 9. {tx-rate}: Connection TX rate\n 10. {security}: Connection Security algorithm\n 11. {signal-quality-bar}: Connection signal quality (percentage bar)\n 12. {channel}: Connection channel number\n 13. {channel-width}: Connection channel width in MHz\n 14. {band}: Connection channel band in GHz",
"type": "string"
},
"wmthemeFormat": {
@@ -1057,6 +1061,116 @@
"description": "Left padding for keys",
"minimum": 0,
"default": 0
},
"language": {
"description": "Language used for module keys. `null` for auto detection based on system locale",
"oneOf": [
{
"type": "null",
"description": "Auto detection based on system locale"
},
{
"type": "string",
"const": "en",
"description": "English"
},
{
"type": "string",
"const": "ar",
"description": "Arabic"
},
{
"type": "string",
"const": "cs",
"description": "Czech"
},
{
"type": "string",
"const": "de",
"description": "German"
},
{
"type": "string",
"const": "es",
"description": "Spanish"
},
{
"type": "string",
"const": "fr",
"description": "French"
},
{
"type": "string",
"const": "gl",
"description": "Galician"
},
{
"type": "string",
"const": "he",
"description": "Hebrew"
},
{
"type": "string",
"const": "id",
"description": "Indonesian"
},
{
"type": "string",
"const": "it",
"description": "Italian"
},
{
"type": "string",
"const": "ja",
"description": "Japanese"
},
{
"type": "string",
"const": "ko",
"description": "Korean"
},
{
"type": "string",
"const": "pl",
"description": "Polish"
},
{
"type": "string",
"const": "pt",
"description": "Portuguese"
},
{
"type": "string",
"const": "ru",
"description": "Russian"
},
{
"type": "string",
"const": "tr",
"description": "Turkish"
},
{
"type": "string",
"const": "uk",
"description": "Ukrainian"
},
{
"type": "string",
"const": "vi",
"description": "Vietnamese"
},
{
"type": "string",
"const": "zh_CN",
"description": "Simplified Chinese"
},
{
"type": "string",
"const": "zh_TW",
"description": "Traditional Chinese"
}
],
"default": "en"
}
}
},
@@ -1482,6 +1596,7 @@
"poweradapter",
"processes",
"publicip",
"top",
"separator",
"shell",
"sound",
@@ -2585,8 +2700,8 @@
},
"waitTime": {
"type": "integer",
"description": "Wait time (in ms). Disk I/O = (totalBytesEnd - totalBytesStart) / waitTime",
"default": 200,
"description": "Wait time (in ms) used to measure the I/O rate (calculated in bytes/sec).\nAvoid setting this too low, as the kernel requires time to accurately update counters.\nIgnored if 'detectTotal' is true.",
"default": 500,
"minimum": 1
},
"key": {
@@ -3486,8 +3601,8 @@
},
"waitTime": {
"type": "integer",
"description": "Wait time (in ms). Net I/O = (totalBytesEnd - totalBytesStart) / waitTime",
"default": 200,
"description": "Wait time (in ms) used to measure the I/O rate (calculated in bytes/sec).\nAvoid setting this too low, as the kernel requires time to accurately update counters.\nIgnored if 'detectTotal' is true.",
"default": 500,
"minimum": 1
},
"key": {
@@ -3648,6 +3763,7 @@
"apk",
"brew",
"choco",
"crux",
"dpkg",
"emerge",
"eopkg",
@@ -3869,7 +3985,12 @@
"properties": {
"type": {
"const": "processes",
"description": "Print the number of running processes"
"description": "Print the number of running processes and threads"
},
"countKprocs": {
"description": "Whether to take kernel processes and threads into account",
"type": "boolean",
"default": false
},
"key": {
"$ref": "#/$defs/key"
@@ -4314,6 +4435,103 @@
}
}
},
{
"title": "Top",
"type": "object",
"additionalProperties": false,
"properties": {
"type": {
"const": "top",
"description": "Print processes with the highest CPU, memory, disk I/O usage, latest start time or thread count"
},
"sort": {
"type": "string",
"description": "Sort processes by CPU, memory, disk IO usage, start time or thread count",
"enum": [
"cpu",
"memory",
"disk-read",
"disk-write",
"start-time",
"threads"
],
"default": "cpu"
},
"showTypes": {
"description": "Specify which resource usages are detected and displayed.\nIf memory or threads is enabled is enabled alone, only a single process snapshot is taken and `waitTime` sampling wait is skipped",
"default": [
"cpu",
"memory",
"disk"
],
"oneOf": [
{
"type": "string",
"enum": [
"cpu",
"memory",
"disk",
"threads"
]
},
{
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"enum": [
"cpu",
"memory",
"disk",
"threads"
]
}
}
]
},
"processes": {
"type": "integer",
"description": "Number of top processes to print",
"minimum": 1,
"default": 5
},
"waitTime": {
"type": "integer",
"description": "Wait time between process samples, in milliseconds. Ignored when showTypes contains \"memory\" only",
"minimum": 1,
"default": 500
},
"compact": {
"type": "boolean",
"description": "Print all process on a single line",
"default": false
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
"keyColor": {
"$ref": "#/$defs/keyColor"
},
"keyIcon": {
"$ref": "#/$defs/keyIcon"
},
"keyWidth": {
"$ref": "#/$defs/keyWidth"
},
"outputColor": {
"$ref": "#/$defs/outputColor"
},
"format": {
"$ref": "#/$defs/topFormat"
},
"condition": {
"$ref": "#/$defs/conditions"
}
}
},
{
"title": "TPM",
"type": "object",
+5 -4
View File
@@ -99,16 +99,17 @@
"gamepad",
"mouse",
"keyboard",
{
"type": "weather",
"timeout": 1000
},
"top",
"netio",
"diskio",
{
"type": "physicaldisk",
"temp": true
},
{
"type": "weather",
"timeout": 1000
},
"tpm",
"version",
"break",
+7 -6
View File
@@ -94,23 +94,24 @@
"opengl",
"opencl",
"users",
// "bluetooth", // doesn't work on macOS because it requires bluetooth permissions
// "bluetoothradio",
"bluetooth",
"bluetoothradio",
"sound",
"camera",
"gamepad",
"mouse",
"keyboard",
{
"type": "weather",
"timeout": 1000
},
"top",
"netio",
"diskio",
{
"type": "physicaldisk",
"temp": true
},
{
"type": "weather",
"timeout": 1000
},
"tpm",
"version",
"logo",
+1 -1
View File
@@ -12,7 +12,7 @@
},
"modules": [
{
// draw borders first to make colors of left and right border consistant
// draw borders first to make colors of left and right border consistent
"key": " user",
"type": "title",
"format": "{user-name}",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"home": "https://github.com/ibireme/yyjson",
"license": "MIT ( embed in source )",
"version": "0.12.0",
"version": "0.13.0",
"author": "ibireme"
}
+700 -285
View File
File diff suppressed because it is too large Load Diff
+911 -674
View File
File diff suppressed because it is too large Load Diff
+4
View File
@@ -31,6 +31,10 @@ typedef struct FFPlatform {
FFstrbuf userShell;
FFPlatformSysinfo sysinfo;
#if _WIN32
uint32_t initCP; // The code page used by the console when the program started
#endif
} FFPlatform;
void ffPlatformInit(FFPlatform* platform);
+2
View File
@@ -8,6 +8,7 @@
#include <stdlib.h>
#include <assert.h>
#include "common/memrchr.h"
#include "common/arrutil.h"
#ifdef FF_USE_SYSTEM_YYJSON
#include <yyjson.h>
@@ -628,3 +629,4 @@ static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) {
bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf);
#define FF_STRBUF_AUTO_DESTROY [[gnu::cleanup(ffStrbufDestroy)]] FFstrbuf
#define FF_STRBUF_STATIC(str) { .allocated = 0, .length = (uint32_t) sizeof(str) - 1, .chars = str }
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <stdint.h>
#if __BIG_ENDIAN__
#define FF_READ_LE(x) _Generic((x), \
uint16_t: __builtin_bswap16(x), \
uint32_t: __builtin_bswap32(x), \
uint64_t: __builtin_bswap64(x))
#define FF_READ_BE(x) (x)
#else
#define FF_READ_LE(x) (x)
#define FF_READ_BE(x) _Generic((x), \
uint16_t: __builtin_bswap16(x), \
uint32_t: __builtin_bswap32(x), \
uint64_t: __builtin_bswap64(x))
#endif
+1
View File
@@ -18,6 +18,7 @@ void ffPlatformInit(FFPlatform* platform) {
#ifdef _WIN32
ffStrbufInit(&platform->sid);
platform->initCP = 0;
#endif
FFPlatformSysinfo* info = &platform->sysinfo;
+41 -28
View File
@@ -3,7 +3,8 @@
#include "common/arrutil.h"
#include "common/strutil.h"
#include "common/io.h"
#include "fastfetch_config.h"
#include "common/path.h"
#include "common/mallocHelper.h"
#include <unistd.h>
#include <pwd.h>
@@ -19,7 +20,6 @@
#elif defined(__OpenBSD__)
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <kvm.h>
#include "common/path.h"
#elif defined(__HAIKU__)
#include <image.h>
@@ -68,13 +68,14 @@ static void getExePath(FFPlatform* platform) {
// Current implementation uses argv[0], which can be easily spoofed.
// See #2195
size_t exePathLen = 0;
kvm_t* kd = kvm_openfiles(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
if (kd) {
int kpCount;
struct kinfo_proc* kp = kvm_getprocs(kd, KERN_PROC_PID, (pid_t) platform->pid, sizeof(*kp), &kpCount);
if (kp && kpCount == 1) {
char** argv = kvm_getargv(kd, kp, 0);
if (argv && argv[0]) {
{
char argvBuf[ARG_MAX];
size_t argvSize = sizeof(argvBuf);
int argvMib[] = { CTL_KERN, KERN_PROC_ARGS, (pid_t) platform->pid, KERN_PROC_ARGV };
if (sysctl(argvMib, ARRAY_SIZE(argvMib), argvBuf, &argvSize, nullptr, 0) == 0) {
// The buffer is filled with an array of char pointers followed by the strings themselves
char** argv = (char**) argvBuf;
if (argv[0] && (char*) argv[0] >= argvBuf && (char*) argv[0] < argvBuf + argvSize) {
char* arg0 = argv[0];
if (arg0[0]) {
if (strchr(arg0, '/') != nullptr) // likely a path (absolute or relative)
@@ -97,25 +98,38 @@ static void getExePath(FFPlatform* platform) {
if (exePathLen > 0) {
struct stat st;
if (stat(exePath, &st) == 0 && S_ISREG(st.st_mode)) {
int cntp;
struct kinfo_file* kf = kvm_getfiles(kd, KERN_FILE_BYPID, (pid_t) platform->pid, sizeof(*kf), &cntp);
if (kf) {
int i;
for (i = 0; i < cntp; i++) {
if (kf[i].fd_fd == KERN_FILE_TEXT) {
// KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list.
if (st.st_dev != (dev_t) kf[i].va_fsid || st.st_ino != (ino_t) kf[i].va_fileid) {
i = -1;
// Replicate kvm_getfiles()'s live path: {CTL_KERN, KERN_FILE, KERN_FILE_BYPID, pid, esize, count}
int fileMib[6] = { CTL_KERN, KERN_FILE, KERN_FILE_BYPID, (pid_t) platform->pid, (int) sizeof(struct kinfo_file), 0 };
size_t fileSize = 0;
if (sysctl(fileMib, ARRAY_SIZE(fileMib), nullptr, &fileSize, nullptr, 0) == 0) {
fileSize += fileSize / 8; // add ~10%
FF_AUTO_FREE struct kinfo_file* kf = (struct kinfo_file*) malloc(fileSize);
if (kf) {
int rv;
do {
fileMib[5] = (int) (fileSize / sizeof(struct kinfo_file));
rv = sysctl(fileMib, ARRAY_SIZE(fileMib), kf, &fileSize, nullptr, 0);
} while (rv == -1 && errno == ENOMEM);
if (rv == 0) {
int cntp = (int) (fileSize / sizeof(struct kinfo_file));
int i;
for (i = 0; i < cntp; i++) {
if (kf[i].fd_fd == KERN_FILE_TEXT) {
// KERN_FILE_TEXT is the executable file, not a shared library, and should be unique in the list.
if (st.st_dev != (dev_t) kf[i].va_fsid || st.st_ino != (ino_t) kf[i].va_fileid) {
i = -1;
}
break;
}
}
if (i < 0) {
exePathLen = 0;
}
break;
}
// If we can't get the list of open files, we can't verify that the file is actually the executable
// Assume it is
}
if (i < 0) {
exePathLen = 0;
}
} else {
// If we can't get the list of open files, we can't verify that the file is actually the executable
// Assume it is
}
} else {
exePathLen = 0;
@@ -124,7 +138,6 @@ static void getExePath(FFPlatform* platform) {
}
}
}
kvm_close(kd);
}
#elif defined(__sun)
ssize_t exePathLen = readlink("/proc/self/path/a.out", exePath, sizeof(exePath) - 1);
@@ -238,8 +251,8 @@ static void getDataDirs(FFPlatform* platform) {
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
platformPathAddEnv(&platform->dataDirs, "XDG_DATA_DIRS");
#ifdef _PATH_LOCALBASE
ffPlatformPathAddAbsolute(&platform->dataDirs, _PATH_LOCALBASE "/share/");
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
ffPlatformPathAddAbsolute(&platform->dataDirs, FF_PATH_PKG_BASE "/share/");
#endif
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/local/share/");
ffPlatformPathAddAbsolute(&platform->dataDirs, FASTFETCH_TARGET_DIR_USR "/share/");
+1
View File
@@ -83,6 +83,7 @@ void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) {
// Round up to the next power of 2.
// If the value is already a power of 2, it will be rounded up to the next power of 2.
// allocate = __builtin_stdc_bit_ceil(allocate + 1); // Currently generates worse asm code
allocate = 1U << (32 - __builtin_clz(allocate));
}
+3 -6
View File
@@ -1,4 +1,5 @@
#include "common/base64.h"
#include "common/endian.h"
// https://github.com/kostya/benchmarks/blob/master/base64/test-nolib.c#L145
void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) {
@@ -7,12 +8,8 @@ void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char*
const char* ends = str + (size - size % 3);
while (str != ends) {
uint32_t n = *(uint32_t*) str;
#if !__BIG_ENDIAN__
// The 3 input bytes must be laid out big-endian (str[0] in the most
// significant position). On little-endian hosts swap; on big-endian
// hosts the word is already in the right order.
n = __builtin_bswap32(n);
#endif
// The 3 input bytes must be laid out big-endian (str[0] in the most significant position).
n = FF_READ_BE(n);
*out++ = chars[(n >> 26) & 63];
*out++ = chars[(n >> 20) & 63];
*out++ = chars[(n >> 14) & 63];
+62 -17
View File
@@ -67,60 +67,71 @@ void ffPrepareCommandOption(FFdata* data) {
char* moduleType = nullptr;
size_t moduleLen = 0;
while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) {
#define FF_IF_MODULE_MATCH(moduleNameConstant) if (moduleLen == strlen(moduleNameConstant) && ffStrEqualsIgnCase(moduleType, moduleNameConstant) && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))
#define FF_IF_MODULE_MATCH(moduleNameConstant) if (ffStrEqualsIgnCase(moduleType, moduleNameConstant) && !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, moduleNameConstant, ':'))
switch (moduleType[0]) {
#if !FF_MODULE_DISABLE_CPUUSAGE
#if !FF_MODULE_DISABLE_CPUUSAGE
case 'C':
case 'c':
FF_IF_MODULE_MATCH(FF_CPUUSAGE_MODULE_NAME)
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
ffPrepareCPUUsage();
break;
#endif
#endif
#if !FF_MODULE_DISABLE_DISKIO
#if !FF_MODULE_DISABLE_DISKIO
case 'D':
case 'd':
FF_IF_MODULE_MATCH(FF_DISKIO_MODULE_NAME) {
FF_IF_MODULE_MATCH(ffDiskIOModuleInfo.name) {
[[gnu::cleanup(ffDestroyDiskIOOptions)]] FFDiskIOOptions options;
ffInitDiskIOOptions(&options);
ffPrepareDiskIO(&options);
}
break;
#endif
#endif
#if !FF_MODULE_DISABLE_NETIO
#if !FF_MODULE_DISABLE_NETIO
case 'N':
case 'n':
FF_IF_MODULE_MATCH(FF_NETIO_MODULE_NAME) {
FF_IF_MODULE_MATCH(ffNetIOModuleInfo.name) {
[[gnu::cleanup(ffDestroyNetIOOptions)]] FFNetIOOptions options;
ffInitNetIOOptions(&options);
ffPrepareNetIO(&options);
}
break;
#endif
#endif
#if !FF_MODULE_DISABLE_PUBLICIP
#if !FF_MODULE_DISABLE_PUBLICIP
case 'P':
case 'p':
FF_IF_MODULE_MATCH(FF_PUBLICIP_MODULE_NAME) {
FF_IF_MODULE_MATCH(ffPublicIPModuleInfo.name) {
[[gnu::cleanup(ffDestroyPublicIpOptions)]] FFPublicIPOptions options;
ffInitPublicIpOptions(&options);
ffPreparePublicIp(&options);
}
break;
#endif
#endif
#if !FF_MODULE_DISABLE_WEATHER
#if !FF_MODULE_DISABLE_TOP
case 'T':
case 't':
FF_IF_MODULE_MATCH(ffTopModuleInfo.name) {
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
ffInitTopOptions(&options);
ffPrepareTopProcesses(options.showTypes);
}
break;
#endif
#if !FF_MODULE_DISABLE_WEATHER
case 'W':
case 'w':
FF_IF_MODULE_MATCH(FF_WEATHER_MODULE_NAME) {
FF_IF_MODULE_MATCH(ffWeatherModuleInfo.name) {
[[gnu::cleanup(ffDestroyWeatherOptions)]] FFWeatherOptions options;
ffInitWeatherOptions(&options);
ffPrepareWeather(&options);
}
break;
#endif
#endif
}
#undef FF_IF_MODULE_MATCH
@@ -167,6 +178,34 @@ static void genJsonResult(FFdata* data, FFModuleBaseInfo* baseInfo, void* option
}
}
static yyjson_val* findStructureModuleConfig(FFdata* data, const char* moduleType) {
if (data->configDoc == nullptr) {
return nullptr;
}
yyjson_val* root = yyjson_doc_get_root(data->configDoc);
if (root == nullptr) {
return nullptr;
}
yyjson_val* modules = yyjson_obj_get(root, "modules");
if (!yyjson_is_arr(modules)) {
return nullptr;
}
yyjson_val* item;
size_t idx, max;
yyjson_arr_foreach (modules, idx, max, item) {
if (yyjson_is_obj(item)) {
const char* type = yyjson_get_str(yyjson_obj_get(item, "type"));
if (type != nullptr && ffStrEqualsIgnCase(type, moduleType)) {
return item;
}
}
}
return nullptr;
}
static bool parseStructureCommand(
FFdata* data,
const char* line,
@@ -180,7 +219,13 @@ static bool parseStructureCommand(
if (data->resultDoc != nullptr) {
fn(data, baseInfo, optionBuf);
} else {
baseInfo->printModule(optionBuf);
yyjson_val* configModule = findStructureModuleConfig(data, baseInfo->name);
if (configModule != nullptr) {
baseInfo->parseJsonObject(optionBuf, configModule);
baseInfo->printModule(optionBuf);
} else {
baseInfo->printModule(optionBuf);
}
}
baseInfo->destroyOptions(optionBuf);
return true;
+1 -1
View File
@@ -996,7 +996,7 @@ bool ffGenConfigInteractive(FFdata* data) {
success = true;
}
} else {
fputs("\nConfig generation cancelled.\n", stderr);
fputs("\nConfig generation canceled.\n", stderr);
}
ffListDestroy(&ui.items);
+5 -13
View File
@@ -5,7 +5,6 @@
#include "common/textModifier.h"
#include "common/strutil.h"
#include "detection/displayserver/displayserver.h"
#include "detection/terminaltheme/terminaltheme.h"
#include "logo/logo.h"
#include <stdlib.h>
@@ -27,21 +26,10 @@ static void initState(FFstate* state) {
state->logoWidth = 0;
state->logoHeight = 0;
state->keysHeight = 0;
state->terminalLightTheme = false;
state->titleFqdn = false;
ffPlatformInit(&state->platform);
state->dynamicInterval = 0;
#if !FF_MODULE_DISABLE_TERMINALTHEME
{
// don't enable bright color if the terminal is in light mode
FFTerminalThemeResult result;
if (ffDetectTerminalTheme(&result, true /* forceEnv for performance */) && !result.bg.dark) {
state->terminalLightTheme = true;
}
}
#endif
}
static void defaultConfig(void) {
@@ -51,7 +39,7 @@ static void defaultConfig(void) {
}
#ifdef _WIN32
static volatile UINT oldCp = CP_UTF8;
static UINT oldCp = CP_UTF8;
void resetConsoleCP(void) {
if (oldCp != CP_UTF8) {
SetConsoleOutputCP(oldCp);
@@ -81,6 +69,10 @@ void ffInitInstance(void) {
defaultConfig();
initState(&instance.state);
#ifdef _WIN32
instance.state.platform.initCP = oldCp;
#endif
}
static volatile bool ffDisableLinewrap = false;
+21 -6
View File
@@ -136,9 +136,9 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
#if !FF_MODULE_DISABLE_CPUUSAGE
case 'c':
case 'C': {
if (ffStrEqualsIgnCase(type, FF_CPUUSAGE_MODULE_NAME)) {
if (ffStrEqualsIgnCase(type, ffCPUUsageModuleInfo.name)) {
ffPrepareCPUUsage();
} else if (ffStrEqualsIgnCase(type, FF_COMMAND_MODULE_NAME)) {
} else if (ffStrEqualsIgnCase(type, ffCommandModuleInfo.name)) {
[[gnu::cleanup(ffDestroyCommandOptions)]] FFCommandOptions options;
ffInitCommandOptions(&options);
if (module) {
@@ -153,7 +153,7 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
#if !FF_MODULE_DISABLE_DISKIO
case 'd':
case 'D': {
if (ffStrEqualsIgnCase(type, FF_DISKIO_MODULE_NAME)) {
if (ffStrEqualsIgnCase(type, ffDiskIOModuleInfo.name)) {
[[gnu::cleanup(ffDestroyDiskIOOptions)]] FFDiskIOOptions options;
ffInitDiskIOOptions(&options);
if (module) {
@@ -168,7 +168,7 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
#if !FF_MODULE_DISABLE_NETIO
case 'n':
case 'N': {
if (ffStrEqualsIgnCase(type, FF_NETIO_MODULE_NAME)) {
if (ffStrEqualsIgnCase(type, ffNetIOModuleInfo.name)) {
[[gnu::cleanup(ffDestroyNetIOOptions)]] FFNetIOOptions options;
ffInitNetIOOptions(&options);
if (module) {
@@ -183,7 +183,7 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
#if !FF_MODULE_DISABLE_PUBLICIP
case 'p':
case 'P': {
if (ffStrEqualsIgnCase(type, FF_PUBLICIP_MODULE_NAME)) {
if (ffStrEqualsIgnCase(type, ffPublicIPModuleInfo.name)) {
[[gnu::cleanup(ffDestroyPublicIpOptions)]] FFPublicIPOptions options;
ffInitPublicIpOptions(&options);
if (module) {
@@ -195,10 +195,25 @@ static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
}
#endif
#if !FF_MODULE_DISABLE_TOP
case 't':
case 'T': {
if (ffStrEqualsIgnCase(type, ffTopModuleInfo.name)) {
[[gnu::cleanup(ffDestroyTopOptions)]] FFTopOptions options;
ffInitTopOptions(&options);
if (module) {
ffTopModuleInfo.parseJsonObject(&options, module);
}
ffPrepareTopProcesses(options.showTypes);
}
break;
}
#endif
#if !FF_MODULE_DISABLE_WEATHER
case 'w':
case 'W': {
if (ffStrEqualsIgnCase(type, FF_WEATHER_MODULE_NAME)) {
if (ffStrEqualsIgnCase(type, ffWeatherModuleInfo.name)) {
[[gnu::cleanup(ffDestroyWeatherOptions)]] FFWeatherOptions options;
ffInitWeatherOptions(&options);
if (module) {
+80
View File
@@ -178,3 +178,83 @@ void* ffLibraryGetModule(const wchar_t* libraryFileName) {
return module;
}
#endif
struct LibraryIterateDynamicLibsBundle {
FFLibraryIterateCallback callback;
void* userData;
};
#if _WIN32
static void ffLibraryIterateDynamicLibsCallback(PLDR_DATA_TABLE_ENTRY DataTableEntry, PVOID Context, BOOLEAN* StopEnumeration) {
if (DataTableEntry->FullDllName.Buffer == nullptr || DataTableEntry->FullDllName.Buffer[0] == L'\0') {
return;
}
char path[PATH_MAX * 3];
ULONG outBytes;
if (NT_SUCCESS(RtlUnicodeToUTF8N(path, sizeof(path), &outBytes, DataTableEntry->FullDllName.Buffer, DataTableEntry->FullDllName.Length))) {
path[outBytes] = '\0';
struct LibraryIterateDynamicLibsBundle* bundle = (struct LibraryIterateDynamicLibsBundle*) Context;
*StopEnumeration = !bundle->callback(path, bundle->userData);
}
}
bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData) {
struct LibraryIterateDynamicLibsBundle bundle = {
.callback = callback,
.userData = userData,
};
return NT_SUCCESS(LdrEnumerateLoadedModules(FALSE, ffLibraryIterateDynamicLibsCallback, &bundle));
}
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData) {
uint32_t imageCount = _dyld_image_count();
for (uint32_t i = 0; i < imageCount; ++i) {
const char* name = _dyld_get_image_name(i);
if (name == nullptr || name[0] == '\0') {
continue;
}
if (!callback(name, userData)) {
break;
}
}
return true;
}
#elif __has_include(<link.h>)
#include <link.h>
static int ffLibraryIterateDynamicLibsCallback(struct dl_phdr_info* info, size_t, void* data) {
if (info->dlpi_name == nullptr || info->dlpi_name[0] == '\0') {
return 0;
}
struct LibraryIterateDynamicLibsBundle* bundle = (struct LibraryIterateDynamicLibsBundle*) data;
return !bundle->callback(info->dlpi_name, bundle->userData);
}
bool ffLibraryIterateDynamicLibs(bool (*callback)(const char* name, void* userData), void* userData) {
dl_iterate_phdr(
ffLibraryIterateDynamicLibsCallback,
&(struct LibraryIterateDynamicLibsBundle) {
.callback = callback,
.userData = userData,
});
return true;
}
#else
bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback, void*) {
// Not implemented for this platform
return false;
}
#endif
+1
View File
@@ -47,6 +47,7 @@ void ffPrintLogoAndKey(const char* moduleName, uint8_t moduleIndex, const FFModu
FF_PARSE_FORMAT_STRING_CHECKED(&key, &moduleArgs->key, ((FFformatarg[]) {
FF_ARG(moduleIndex, "index"),
FF_ARG(moduleArgs->keyIcon, "icon"),
FF_ARG(moduleName, "module-name"),
}));
ffStrbufWriteTo(&key, stdout);
}
+16 -19
View File
@@ -28,7 +28,6 @@
#elif defined(__OpenBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <kvm.h>
#elif defined(__NetBSD__)
#include <sys/types.h>
#include <sys/sysctl.h>
@@ -423,12 +422,13 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons
#elif defined(__OpenBSD__)
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
int count = 0;
const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), &count);
if (proc) {
char** argv = kvm_getargv(kd, proc, 0);
if (argv) {
char argvBuf[ARG_MAX];
size_t argvSize = sizeof(argvBuf);
int argvMib[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV };
if (sysctl(argvMib, ARRAY_SIZE(argvMib), argvBuf, &argvSize, nullptr, 0) == 0) {
// The buffer is filled with an array of char pointers followed by the strings themselves
char** argv = (char**) argvBuf;
if (argv[0] && (char*) argv[0] >= argvBuf && (char*) argv[0] < argvBuf + argvSize) {
const char* arg0 = argv[0];
if (arg0[0] == '-') {
arg0++;
@@ -436,7 +436,6 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons
ffStrbufSetS(exe, arg0);
}
}
kvm_close(kd);
#elif defined(__HAIKU__)
@@ -633,21 +632,19 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
#elif defined(__OpenBSD__)
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
int count = 0;
const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc), &count);
if (proc) {
ffStrbufSetS(name, proc->p_comm);
struct kinfo_proc proc;
size_t size = sizeof(proc);
int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid, (int) sizeof(struct kinfo_proc), 1 };
if (sysctl(mib, ARRAY_SIZE(mib), &proc, &size, nullptr, 0) == 0) {
ffStrbufSetS(name, proc.p_comm);
if (ppid) {
*ppid = proc->p_ppid;
*ppid = proc.p_ppid;
}
if (tty) {
*tty = (int) proc->p_tdev;
*tty = (int) proc.p_tdev;
}
}
kvm_close(kd);
if (!proc) {
return "kvm_getprocs() failed";
} else {
return "sysctl(KERN_PROC_PID) failed";
}
#elif defined(__HAIKU__)
+46 -10
View File
@@ -48,6 +48,41 @@ static void argvToCmdline(char* const argv[], FFstrbuf* result) {
}
}
static wchar_t* createChildEnvironment(void) {
auto pparams = ffGetPeb()->ProcessParameters;
const wchar_t* readStart = pparams->Environment;
if (!readStart) {
return nullptr;
}
RtlAcquirePebLock();
wchar_t* result = malloc(pparams->EnvironmentSize + sizeof(L"LANG=C.UTF-8"));
const wchar_t* entry = readStart;
wchar_t* writeStart = result;
// Copy all environment variables except LANG
while (*entry) {
assert((const uint8_t*) entry <= (const uint8_t*) pparams->Environment + pparams->EnvironmentSize);
size_t entryLength = wcslen(entry);
if (entryLength >= strlen("LANG=") && memcmp(entry, L"LANG=", strlen("LANG=") * sizeof(wchar_t)) == 0) {
memcpy(writeStart, readStart, (size_t) (entry - readStart) * sizeof(wchar_t));
writeStart += (size_t) (entry - readStart);
readStart = entry + entryLength + 1;
}
entry += entryLength + 1;
}
// Copy the remaining environment variables
memcpy(writeStart, readStart, (size_t) (entry - readStart) * sizeof(wchar_t));
writeStart += (size_t) (entry - readStart);
// Add LANG=C.UTF-8 and double null terminator
memcpy(writeStart, L"LANG=C.UTF-8\0", sizeof(L"LANG=C.UTF-8\0"));
RtlReleasePebLock();
return result;
}
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle* outHandle) {
const int32_t timeout = instance.config.general.processingTimeout;
@@ -108,17 +143,18 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFProcessHandle*
}
}
FF_AUTO_FREE wchar_t* environment = createChildEnvironment();
BOOL success = CreateProcessW(
nullptr, // application name
cmdline, // command line
nullptr, // process security attributes
nullptr, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
nullptr, // use parent's environment
nullptr, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo // receives PROCESS_INFORMATION
nullptr, // application name
cmdline, // command line
nullptr, // process security attributes
nullptr, // primary thread security attributes
TRUE, // handles are inherited
CREATE_UNICODE_ENVIRONMENT, // creation flags
environment, // child environment
nullptr, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo // receives PROCESS_INFORMATION
);
NtClose(hChildPipeWrite);
+4 -6
View File
@@ -450,12 +450,10 @@ bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf*
return true;
}
#else // FF_HAVE_SQLITE3
int ffSettingsGetSQLite3Int(const char* dbPath, const char* query) {
FF_UNUSED(dbPath, query)
int ffSettingsGetSQLite3Int(const char*, const char*) {
return 0;
}
bool ffSettingsGetSQLite3String(const char* dbPath, const char* query, FFstrbuf* result) {
FF_UNUSED(dbPath, query, result)
bool ffSettingsGetSQLite3String(const char*, const char*, FFstrbuf*) {
return false;
}
#endif // FF_HAVE_SQLITE3
@@ -509,8 +507,8 @@ typedef struct E_Config {
Eina_List* font_defaults;
} E_Config; // Must be the same name as the top level struct in e.cfg
#define FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(clas, type) \
(ffeet_eina_file_data_descriptor_class_set(clas, sizeof(*(clas)), #type, sizeof(type)))
#define FF_EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(klass, type) \
(ffeet_eina_file_data_descriptor_class_set(klass, sizeof(*(klass)), #type, sizeof(type)))
#define FF_EET_DATA_DESCRIPTOR_ADD_BASIC(edd, struct_type, member, type) \
do { \
struct_type ___ett; \
+17 -16
View File
@@ -1,3 +1,4 @@
#include "common/endian.h"
#include "common/smbios.h"
#include "common/io.h"
#include "common/mallocHelper.h"
@@ -81,9 +82,9 @@ static bool parseSmbiosTable(const uint8_t* data, uint32_t length) {
break;
}
if (header->Handle >= 0xFF00) {
if (FF_READ_LE(header->Handle) >= 0xFF00) {
FF_DEBUG("Invalid SMBIOS structure handle 0x%04x at offset 0x%lx",
header->Handle,
FF_READ_LE(header->Handle),
(unsigned long) ((const uint8_t*) header - data));
break;
}
@@ -103,25 +104,25 @@ static bool parseSmbiosTable(const uint8_t* data, uint32_t length) {
smbiosTable[header->Type] = header;
FF_DEBUG("Found SMBIOS structure type %u, handle 0x%04X, length %u",
header->Type,
header->Handle,
FF_READ_LE(header->Handle),
header->Length);
structureCount++;
} else {
FF_DEBUG("Duplicate SMBIOS structure type %u, handle 0x%04X, length %u",
header->Type,
header->Handle,
FF_READ_LE(header->Handle),
header->Length);
}
} else if (header->Type == FF_SMBIOS_TYPE_END_OF_TABLE) {
FF_DEBUG("Reached SMBIOS end of type %u, handle 0x%04X, length %u",
header->Type,
header->Handle,
FF_READ_LE(header->Handle),
header->Length);
break;
} else {
FF_DEBUG("Found custom SMBIOS structure type %u, handle 0x%04X, length %u; ignoring",
header->Type,
header->Handle,
FF_READ_LE(header->Handle),
header->Length);
}
}
@@ -308,8 +309,8 @@ static bool fillTableBufferFallback(FFstrbuf* buffer) {
sizeof(p->Smbios30));
return false;
}
tableLength = p->Smbios30.StructureTableMaximumSize;
tableAddress = (off_t) p->Smbios30.StructureTableAddress;
tableLength = FF_READ_LE(p->Smbios30.StructureTableMaximumSize);
tableAddress = (off_t) FF_READ_LE(p->Smbios30.StructureTableAddress);
FF_DEBUG("SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u",
tableLength,
(unsigned long) tableAddress,
@@ -325,8 +326,8 @@ static bool fillTableBufferFallback(FFstrbuf* buffer) {
sizeof(p->Smbios20));
return false;
}
tableLength = p->Smbios20.StructureTableLength;
tableAddress = (off_t) p->Smbios20.StructureTableAddress;
tableLength = FF_READ_LE(p->Smbios20.StructureTableLength);
tableAddress = (off_t) FF_READ_LE(p->Smbios20.StructureTableAddress);
FF_DEBUG("SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u",
tableLength,
(unsigned long) tableAddress,
@@ -369,9 +370,9 @@ static bool detectSmbiosTableLength(const uint8_t* data, uint32_t bufferLength,
return false;
}
if (header->Handle >= 0xFF00) {
if (FF_READ_LE(header->Handle) >= 0xFF00) {
FF_DEBUG("Invalid SMBIOS structure handle 0x%04x at offset 0x%lx",
header->Handle,
FF_READ_LE(header->Handle),
(unsigned long) (p - data));
return false;
}
@@ -614,8 +615,8 @@ static bool fillTableBufferPlatform(FFstrbuf* buffer) {
sizeof(entryPoint.Smbios20));
return false;
}
tableLength = entryPoint.Smbios20.StructureTableLength;
tableAddress = (off_t) entryPoint.Smbios20.StructureTableAddress;
tableLength = FF_READ_LE(entryPoint.Smbios20.StructureTableLength);
tableAddress = (off_t) FF_READ_LE(entryPoint.Smbios20.StructureTableAddress);
FF_DEBUG("SMBIOS 2.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u",
tableLength,
(unsigned long) tableAddress,
@@ -629,8 +630,8 @@ static bool fillTableBufferPlatform(FFstrbuf* buffer) {
sizeof(entryPoint.Smbios30));
return false;
}
tableLength = entryPoint.Smbios30.StructureTableMaximumSize;
tableAddress = (off_t) entryPoint.Smbios30.StructureTableAddress;
tableLength = FF_READ_LE(entryPoint.Smbios30.StructureTableMaximumSize);
tableAddress = (off_t) FF_READ_LE(entryPoint.Smbios30.StructureTableAddress);
FF_DEBUG("SMBIOS 3.0: tableLength=0x%x, tableAddress=0x%lx, version=%u.%u.%u",
tableLength,
(unsigned long) tableAddress,
+48 -23
View File
@@ -22,21 +22,41 @@ const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) {
}
int ffSysctlGetInt(int mib1, int mib2, int defaultValue) {
int result;
alignas(int) uint8_t result[sizeof(int)];
size_t neededLength = sizeof(result);
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) {
int64_t result;
alignas(int64_t) uint8_t result[sizeof(int64_t)];
size_t neededLength = sizeof(result);
if (sysctl((int[]) { mib1, mib2 }, 2, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int64_t):
return *(int64_t*)result;
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
#else
const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
@@ -59,35 +79,40 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
}
int ffSysctlGetInt(const char* propName, int defaultValue) {
int result;
alignas(int) uint8_t result[sizeof(int)];
size_t neededLength = sizeof(result);
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue) {
int64_t result;
alignas(int64_t) uint8_t result[sizeof(int64_t)];
size_t neededLength = sizeof(result);
if (sysctlbyname(propName, &result, &neededLength, nullptr, 0) != 0) {
return defaultValue;
}
return result;
switch (neededLength) {
case sizeof(int64_t):
return *(int64_t*)result;
case sizeof(int32_t):
return *(int32_t*)result;
case sizeof(int16_t):
return *(int16_t*)result;
case sizeof(int8_t):
return *(int8_t*)result;
default:
return defaultValue;
}
}
#endif // OpenBSD
void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength) {
if (sysctl(request, requestLength, nullptr, resultLength, nullptr, 0) != 0) {
return nullptr;
}
void* data = malloc(*resultLength);
if (sysctl(request, requestLength, data, resultLength, nullptr, 0) != 0) {
free(data);
return nullptr;
}
return data;
}
+6 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fastfetch.h"
#include "fastfetch.h" // IWYU pragma: keep
#ifndef FF_DISABLE_DLOPEN
@@ -111,3 +111,8 @@ void* ffLibraryLoadMulti(const char* path, int maxVersion, ...);
#if _WIN32
void* ffLibraryGetModule(const wchar_t* libraryFileName);
#endif
// Return false to stop iterating, true to continue
typedef bool (*FFLibraryIterateCallback)(const char* name, void* userData);
// Iterate over all loaded dynamic libraries. Returns true on success, false on failure.
bool ffLibraryIterateDynamicLibs(FFLibraryIterateCallback callback, void* userData);
+27
View File
@@ -18,10 +18,35 @@ typedef struct FFModuleFormatArgList {
#define FF_FORMAT_ARG_LIST(list) { .args = list, .count = sizeof(list) / sizeof(FFModuleFormatArg) }
typedef struct FFModuleDisplayName {
const char* en; // English
const char* ar; // Arabic
const char* cs; // Czech
const char* de; // German
const char* es; // Spanish
const char* fr; // French
const char* gl; // Galician
const char* he; // Hebrew
const char* id; // Indonesian
const char* it; // Italian
const char* ja; // Japanese
const char* ko; // Korean
const char* pl; // Polish
const char* pt; // (Brazilian) Portuguese
const char* ru; // Russian
const char* tr; // Turkish
const char* uk; // Ukrainian
const char* vi; // Vietnamese
const char* zh_CN; // Simplified Chinese
const char* zh_TW; // Traditional Chinese
} FFModuleDisplayName;
// Must be the first field of FFModuleOptions
typedef struct FFModuleBaseInfo {
const char* name;
const char* description;
FFModuleDisplayName displayName;
// A dirty polymorphic implementation in C.
// This is UB, because `void*` is not compatible with `FF*Options*`.
// However we can't do it better unless we move to C++, so that `option` becomes a `this` pointer
@@ -94,3 +119,5 @@ static inline void ffOptionDestroyModuleArg(FFModuleArgs* args) {
}
enum { FF_OPTION_MAX_SIZE = 1 << 8 }; // Maximum size of a single option value, used for static allocation
#define FF_MODULE_GET_DISPLAY_NAME(moduleName) (*(const char**) ((uint8_t*) &ff ## moduleName ## ModuleInfo.displayName + instance.config.display.keyLanguage))
+16
View File
@@ -2,6 +2,7 @@
#include "common/FFstrbuf.h"
#include "common/strutil.h"
#include "fastfetch_config.h"
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result);
static inline bool ffIsAbsolutePath(const char* path) {
@@ -19,3 +20,18 @@ char* realpath(const char* __restrict file_name, char* __restrict resolved_name
ssize_t freadlink(void* hFile, char* buf, size_t bufsiz);
ssize_t readlink(const char* path, char* buf, size_t bufsiz);
#endif
#ifdef __FreeBSD__
#include <paths.h>
#ifdef _PATH_LOCALBASE
#define FF_PATH_PKG_BASE _PATH_LOCALBASE
#else
#define FF_PATH_PKG_BASE "/usr/local"
#endif
#elif __OpenBSD__
#define FF_PATH_PKG_BASE "/usr/local"
#elif __NetBSD__
#define FF_PATH_PKG_BASE "/usr/pkg"
#else
#define FF_PATH_PKG_BASE FASTFETCH_TARGET_DIR_USR
#endif
-1
View File
@@ -14,4 +14,3 @@ const char* ffSysctlGetString(const char* propName, FFstrbuf* result);
[[nodiscard]] int ffSysctlGetInt(const char* propName, int defaultValue);
[[nodiscard]] int64_t ffSysctlGetInt64(const char* propName, int64_t defaultValue);
#endif
[[nodiscard]] void* ffSysctlGetData(int* request, u_int requestLength, size_t* resultLength);
+99 -4
View File
@@ -11,7 +11,10 @@ enum {
SystemFirmwareTableInformation = 76,
SystemBootEnvironmentInformation = 90,
SystemLogicalProcessorAndGroupInformation = 107,
SystemBasicPerformanceInformation = 123,
SystemSecureBootInformation = 146,
SystemBasicProcessInformation = 252,
SystemHandleCountInformation = 253,
};
typedef struct _PROCESSOR_POWER_INFORMATION {
@@ -169,7 +172,7 @@ NTSTATUS NTAPI NtQuerySystemEnvironmentValueEx(
);
NTSTATUS NTAPI RtlGUIDFromString(IN PCUNICODE_STRING GuidString, OUT GUID* Guid);
NTSTATUS NTAPI RtlStringFromGUIDEx(IN GUID* Guid, OUT PCUNICODE_STRING GuidString, _In_ BOOLEAN AllocateGuidString);
NTSTATUS NTAPI RtlStringFromGUIDEx(IN const GUID* Guid, OUT PUNICODE_STRING GuidString, IN BOOLEAN AllocateGuidString);
typedef struct _SYSTEM_SECUREBOOT_INFORMATION {
BOOLEAN SecureBootEnabled;
@@ -198,6 +201,27 @@ typedef struct _SYSTEM_FIRMWARE_TABLE_INFORMATION {
_Field_size_bytes_(TableBufferLength) UCHAR TableBuffer[];
} SYSTEM_FIRMWARE_TABLE_INFORMATION, *PSYSTEM_FIRMWARE_TABLE_INFORMATION;
typedef struct _SYSTEM_BASIC_PERFORMANCE_INFORMATION {
SIZE_T AvailablePages;
SIZE_T CommittedPages;
SIZE_T CommitLimit;
SIZE_T PeakCommitment;
} SYSTEM_BASIC_PERFORMANCE_INFORMATION, *PSYSTEM_BASIC_PERFORMANCE_INFORMATION;
typedef struct _SYSTEM_BASICPROCESS_INFORMATION {
ULONG NextEntryOffset;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
ULONG64 SequenceNumber;
UNICODE_STRING ImageName;
} SYSTEM_BASICPROCESS_INFORMATION, *PSYSTEM_BASICPROCESS_INFORMATION;
typedef struct _SYSTEM_HANDLECOUNT_INFORMATION {
ULONG ProcessCount;
ULONG ThreadCount;
ULONG HandleCount;
} SYSTEM_HANDLECOUNT_INFORMATION, *PSYSTEM_HANDLECOUNT_INFORMATION;
NTSYSAPI NTSTATUS NTAPI NtDelayExecution(_In_ BOOLEAN Alertable, _In_ PLARGE_INTEGER DelayInterval);
/**
@@ -599,6 +623,54 @@ typedef struct _KUSER_SHARED_DATA {
};
};
//
// Reserved padding field to preserve structure alignment and compatibility.
//
ULONG DataFlagsPad[1];
//
// Depending on the processor, the code for fast system call will differ,
// Stub code is provided pointers below to access the appropriate code.
//
// N.B. The following field is only used on 32-bit systems.
//
ULONGLONG TestRetInstruction;
//
// Query-performance counter (QPC) frequency, in counts per second.
//
// N.B. This value represents the fixed frequency of the system's high-resolution
// performance counter. It is used by user-mode time routines to convert QPC
// ticks into elapsed time without requiring a system call. The frequency is
// constant for the lifetime of the system and reflects the hardware or
// virtualized timer source selected by the kernel.
//
LONGLONG QpcFrequency;
//
// On AMD64, this value is initialized to a nonzero value if the system
// operates with an altered view of the system service call mechanism.
//
ULONG SystemCall;
//
// Reserved field - do not use. Used to be UserCetAvailableEnvironments.
//
ULONG Reserved2;
//
// Full 64 bit version of the number of physical pages in the system.
// This can dynamically change as physical memory can be added or removed
// from a running system.
//
ULONGLONG FullNumberOfPhysicalPages;
// ... more fields follow, but we don't need them
} KUSER_SHARED_DATA, *PKUSER_SHARED_DATA;
@@ -635,15 +707,19 @@ static inline uint64_t ffKSystemTimeToUInt64(const volatile KSYSTEM_TIME* pTime)
static inline bool ffIsWindows10OrGreater() {
#if FF_WIN81_COMPAT
return SharedUserData->NtMajorVersion >= 10;
return SharedUserData->NtBuildNumber >= 10240;
#else
return true;
#endif
}
static inline bool ffIsWindows11OrGreater() {
return SharedUserData->NtMajorVersion > 10 ||
(SharedUserData->NtMajorVersion == 10 && SharedUserData->NtBuildNumber >= 22000);
return SharedUserData->NtBuildNumber >= 22000;
}
static inline bool ffIsSystemBasicProcessInfoAvailable() { // Includes HandleCountInformation, which was added together
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation#systembasicprocessinformation
return SharedUserData->NtBuildNumber >= 26200; // MSDN says it was added in 26100.4770; ntdoc says 25H2
}
NTSYSAPI NTSTATUS NTAPI NtOpenProcessToken(
@@ -1279,6 +1355,22 @@ NTSYSAPI NTSTATUS NTAPI LdrGetProcedureAddress(
_In_opt_ ULONG ProcedureNumber,
_Out_ PVOID* ProcedureAddress);
typedef _Function_class_(LDR_LOADED_MODULE_ENUMERATION_CALLBACK_FUNCTION)
VOID NTAPI LDR_LOADED_MODULE_ENUMERATION_CALLBACK_FUNCTION(
_In_ PLDR_DATA_TABLE_ENTRY DataTableEntry,
_In_opt_ PVOID Context,
_Inout_ BOOLEAN* StopEnumeration
);
NTSYSAPI
NTSTATUS
NTAPI
LdrEnumerateLoadedModules(
_In_ BOOLEAN ReservedFlag,
_In_ LDR_LOADED_MODULE_ENUMERATION_CALLBACK_FUNCTION* EnumProc,
_In_opt_ PVOID Context
);
typedef enum _SECTION_INHERIT {
ViewShare = 1,
ViewUnmap = 2
@@ -1323,3 +1415,6 @@ NTSYSAPI NTSTATUS NTAPI NtCancelIoFileEx(
NTSYSAPI NTSTATUS NTAPI NtTerminateProcess(
_In_opt_ HANDLE ProcessHandle,
_In_ NTSTATUS ExitStatus);
NTSYSAPI NTSTATUS NTAPI RtlAcquirePebLock(VOID);
NTSYSAPI NTSTATUS NTAPI RtlReleasePebLock(VOID);
+113
View File
@@ -0,0 +1,113 @@
#include "battery.h"
#include <kstat.h>
static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
assert(pkc);
if (*pkc) {
kstat_close(*pkc);
}
}
static bool getUint32(kstat_t* ksp, const char* name, uint32_t* value) {
kstat_named_t* stat = kstat_data_lookup(ksp, name);
if (!stat) {
return false;
}
switch (stat->data_type) {
case KSTAT_DATA_UINT32:
*value = stat->value.ui32;
return true;
case KSTAT_DATA_INT32:
if (stat->value.i32 >= 0) {
*value = (uint32_t) stat->value.i32;
return true;
}
break;
#ifdef _INT64_TYPE
case KSTAT_DATA_UINT64:
if (stat->value.ui64 <= UINT32_MAX) {
*value = (uint32_t) stat->value.ui64;
return true;
}
break;
case KSTAT_DATA_INT64:
if (stat->value.i64 >= 0 && (uint64_t) stat->value.i64 <= UINT32_MAX) {
*value = (uint32_t) stat->value.i64;
return true;
}
break;
#endif
default:
break;
}
return false;
}
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
// https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/powertop/common/battery.c
[[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
if (!kc) {
return "kstat_open() failed";
}
// illumos exposes battery information through either of these modules.
const char* modules[] = { "battery", "acpi_drv" };
kstat_t* bif = nullptr;
kstat_t* bst = nullptr;
for (size_t i = 0; i < ARRAY_SIZE(modules); ++i) {
bif = kstat_lookup(kc, modules[i], 0, "battery BIF0");
bst = kstat_lookup(kc, modules[i], 0, "battery BST0");
if (bif && bst) {
break;
}
bif = nullptr;
bst = nullptr;
}
if (!bif || !bst || kstat_read(kc, bif, nullptr) < 0 || kstat_read(kc, bst, nullptr) < 0) {
return nullptr;
}
uint32_t lastCapacity, remainingCapacity, rate, state;
if (!getUint32(bif, "bif_last_cap", &lastCapacity) ||
!getUint32(bst, "bst_rem_cap", &remainingCapacity) ||
!getUint32(bst, "bst_state", &state)) {
return "kstat_data_lookup() failed";
}
if (lastCapacity == 0 || remainingCapacity == UINT32_MAX) {
return nullptr;
}
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
ffStrbufInit(&battery->manufacturer);
ffStrbufInit(&battery->manufactureDate);
ffStrbufInit(&battery->modelName);
ffStrbufInit(&battery->technology);
ffStrbufInit(&battery->serial);
battery->status = FF_BATTERY_STATUS_NONE;
battery->capacity = (double) remainingCapacity * 100.0 / (double) lastCapacity;
battery->temperature = FF_BATTERY_TEMP_UNSET;
battery->cycleCount = 0;
battery->timeRemaining = -1;
// illumos BST state: bit 0 discharging, bit 1 charging, bit 2 critical.
if (state & (1u << 0)) {
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
}
if (state & (1u << 1)) {
battery->status |= FF_BATTERY_STATUS_CHARGING;
}
if (state & (1u << 2)) {
battery->status |= FF_BATTERY_STATUS_CRITICAL;
}
if ((state & (1u << 0)) && getUint32(bst, "bst_rate", &rate) && rate > 0 && rate != UINT32_MAX) {
battery->timeRemaining = (int32_t) (((uint64_t) remainingCapacity * 3600) / rate);
}
return nullptr;
}
+8 -12
View File
@@ -1,6 +1,7 @@
#define INITGUID
#include "battery.h"
#include "common/endian.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
@@ -127,29 +128,24 @@ static void detectStaticData(FFlist* entries, FFlist* results) {
FFBatteryWmiEntry* entry = getBatteryEntry(entries, results, data->Tag);
FF_DEBUG("chemistry: %.4s", (const char*) &data->Chemistry);
#if __BIG_ENDIAN__
#define htobe32(x) (x) // Should not happen on Windows, but just in case
#else
#define htobe32(x) __builtin_bswap32(x)
#endif
switch (data->Chemistry) {
case htobe32('PbAc'):
case FF_READ_BE((uint32_t) 'PbAc'):
ffStrbufSetStatic(&entry->result->technology, "Lead Acid");
break;
case htobe32('LION'):
case htobe32('Li-I'):
case FF_READ_BE((uint32_t) 'LION'):
case FF_READ_BE((uint32_t) 'Li-I'):
ffStrbufSetStatic(&entry->result->technology, "Lithium Ion");
break;
case htobe32('NiCd'):
case FF_READ_BE((uint32_t) 'NiCd'):
ffStrbufSetStatic(&entry->result->technology, "Nickel Cadmium");
break;
case htobe32('NiMH'):
case FF_READ_BE((uint32_t) 'NiMH'):
ffStrbufSetStatic(&entry->result->technology, "Nickel Metal Hydride");
break;
case htobe32('NiZn'):
case FF_READ_BE((uint32_t) 'NiZn'):
ffStrbufSetStatic(&entry->result->technology, "Nickel Zinc");
break;
case htobe32('RAM\0'):
case FF_READ_BE((uint32_t) 'RAM\0'):
ffStrbufSetStatic(&entry->result->technology, "Rechargeable Alkaline-Manganese");
break;
default:
+2 -6
View File
@@ -19,12 +19,8 @@ const char* ffDetectCamera(FFlist* result)
AVCaptureDeviceType deviceType = nullptr;
#ifdef MAC_OS_VERSION_14_0
// Strangely `@available(macOS 14.0, *)` doesn't work here (#1594)
if (@available(macOS 14.0, *))
{
if (&AVCaptureDeviceTypeExternal)
deviceType = AVCaptureDeviceTypeExternal;
}
if (&AVCaptureDeviceTypeExternal)
deviceType = AVCaptureDeviceTypeExternal;
#endif
if (deviceType == nullptr)
deviceType = AVCaptureDeviceTypeExternalUnknown;
+2 -1
View File
@@ -1,4 +1,5 @@
#include "chassis.h"
#include "common/endian.h"
#include "common/io.h"
#include "common/smbios.h"
@@ -21,7 +22,7 @@ const char* ffDetectChassis(FFChassisResult* result) {
uint32_t chassisType = 0;
if (ffReadFileData("/sys/firmware/devicetree/base/smbios/smbios/chassis/chassis-type", sizeof(chassisType), &chassisType)) // big endian
{
chassisType = __builtin_bswap32(chassisType);
chassisType = FF_READ_BE(chassisType);
const char* typeStr = ffChassisTypeToString(chassisType);
if (typeStr) {
ffStrbufSetStatic(&result->type, typeStr);
+7 -1
View File
@@ -3,6 +3,10 @@
#include <VideoToolbox/VideoToolbox.h>
#include "common/apple/cf_helpers.h"
#ifdef MAC_OS_VERSION_11_0
[[clang::weak_import]] VT_EXPORT void VTRegisterSupplementalVideoDecoderIfAvailable(CMVideoCodecType codecType);
#endif
static const struct {
CMVideoCodecType codec;
FFCodecType type;
@@ -70,9 +74,11 @@ static FFCodecType ffCodecDetectDecoders() {
continue;
}
if (__builtin_available(macOS 11.0, *)) {
#ifdef MAC_OS_VERSION_11_0
if (VTRegisterSupplementalVideoDecoderIfAvailable) {
VTRegisterSupplementalVideoDecoderIfAvailable(codec.codec);
}
#endif
bool supported = VTIsHardwareDecodeSupported(codec.codec);
if (!supported) {
continue;
+4
View File
@@ -62,6 +62,10 @@ const char* ffCPUAppleCodeToName(uint32_t code) {
return "Apple M4 Pro";
case 6041:
return "Apple M4 Max";
case 8142:
return "Apple M5";
case 6050:
return "Apple M5 Pro / Max";
default:
return nullptr;
}
+86 -18
View File
@@ -214,9 +214,15 @@ static void detectQualcomm(FFCPUResult* cpu) {
const char* name = nullptr;
switch (code) {
case 8975:
name = "8 Elite Extreme Gen 6";
break;
case 8950:
name = "8 Elite Gen 6";
break;
case 8845:
name = "8 Gen 5";
break; // ?
break;
case 8850:
name = "8 Elite Gen 5";
break;
@@ -238,6 +244,9 @@ static void detectQualcomm(FFCPUResult* cpu) {
case 8475:
name = "8+ Gen 1";
break;
case 8425:
name = "8+ Gen 1 4G";
break;
case 8450:
name = "8 Gen 1";
break;
@@ -271,12 +280,18 @@ static void detectQualcomm(FFCPUResult* cpu) {
case 6475:
name = "6 Gen 3";
break;
case 6225:
name = "6s 4G Gen 2";
break;
case 6115:
name = "6s Gen 1";
name = "6s 4G Gen 1";
break;
case 6450:
name = "6 Gen 1";
break;
case 4850:
name = "4 Gen 5";
break;
case 4635:
name = "4s Gen 2";
break;
@@ -305,6 +320,9 @@ static void detectMediaTek(FFCPUResult* cpu) {
switch (code) // The SOC code of MTK Dimensity series is full of mess
{
case 6995:
name = "9600";
break;
case 6993:
name = "9500";
break;
@@ -357,6 +375,9 @@ static void detectExynos(FFCPUResult* cpu) {
const char* name = nullptr;
switch (code) {
case 9975:
name = "2700";
break;
case 9965:
name = "2600";
break;
@@ -374,6 +395,9 @@ static void detectExynos(FFCPUResult* cpu) {
name = "2100";
break;
case 8865:
name = "1680";
break;
case 8855:
name = "1580";
break;
@@ -595,6 +619,9 @@ static const char* parseCpuInfo(
(cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
#elif __sh__
(cpu->name.length == 0 && ffParsePropLine(line, "cpu type :", &cpu->name)) ||
#elif __sparc__ || __sparc
(cpu->name.length == 0 && ffParsePropLine(line, "cpu :", &cpu->name)) ||
(cpuMHz->length == 0 && ffParsePropLine(line, "Cpu0ClkTck :", cpuMHz)) ||
#else
(cpu->name.length == 0 && ffParsePropLine(line, "model name :", &cpu->name)) ||
(cpu->name.length == 0 && ffParsePropLine(line, "model :", &cpu->name)) ||
@@ -688,7 +715,7 @@ static bool detectFrequency(FFCPUResult* cpu, const FFCPUOptions* options) {
while ((p = memmem(p, cpuinfo->length - (uint32_t) (p - cpuinfo->chars), "\nphysical id\t:", strlen("\nphysical id\t:")))) {
p += strlen("\nphysical id\t:");
char* pend;
unsigned long long id = strtoul(p, &pend, 10);
unsigned long id = strtoul(p, &pend, 10);
if (__builtin_expect(id > 64, false)) { // Do 129-socket boards exist?
high |= 1ULL << (id - 64);
} else {
@@ -813,6 +840,46 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) {
return nullptr;
}
[[maybe_unused]] static bool detectSnapdragonX(FFstrbuf* name, const char* model) {
// SoC models are enumerated in the form of `x<gen>[e|p]<part><rev>`, e.g. `x1e80100`,
// which is marketed as `X1E-80-100`, where `e` stands for Elite and `p` for Plus.
// https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips#Snapdragon_X_series
const char* prefix;
uint32_t prefixLen;
if (ffStrStartsWith(model, "x1e")) {
prefix = "Qualcomm Snapdragon X Elite";
prefixLen = 3;
} else if (ffStrStartsWith(model, "x1p")) {
prefix = "Qualcomm Snapdragon X Plus";
prefixLen = 3;
} else if (ffStrStartsWith(model, "x2e")) {
// Only X2E-94-100 and X2E-96-100 are branded as Extreme
prefix = model[3] == '9' ? "Qualcomm Snapdragon X2 Elite Extreme" : "Qualcomm Snapdragon X2 Elite";
prefixLen = 3;
} else if (ffStrStartsWith(model, "x2p")) {
prefix = "Qualcomm Snapdragon X2 Plus";
prefixLen = 3;
} else if (ffStrStartsWith(model, "x1")) {
prefix = "Qualcomm Snapdragon X";
prefixLen = 2;
} else {
return false;
}
uint32_t length = (uint32_t) strlen(model);
bool splitCode = length - prefixLen == 5;
ffStrbufSetF(name, "%s X", prefix);
for (uint32_t i = 1; i < length; ++i) {
if (i == prefixLen || (splitCode && i == length - 3)) {
ffStrbufAppendC(name, '-');
}
ffStrbufAppendC(name, (char) toupper(model[i]));
}
return true;
}
[[maybe_unused]] static void parseIsa(FFstrbuf* cpuIsa) {
// Always use the last part of the ISA string. Ref: #590 #1204
ffStrbufSubstrAfterLastC(cpuIsa, ' ');
@@ -914,22 +981,19 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) {
}
#endif
else if (ffStrEquals(vendor, "qcom")) {
// https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom
if (ffStrStartsWith(model, "x")) {
ffStrbufSetS(&cpu->name, "Qualcomm Snapdragon X Elite ");
for (const char* p = model + 1; *p; ++p) {
ffStrbufAppendC(&cpu->name, (char) toupper(*p));
// https://elixir.bootlin.com/linux/latest/source/arch/arm64/boot/dts/qcom
if (!detectSnapdragonX(&cpu->name, model)) {
if (ffStrStartsWith(model, "sc")) {
const char* code = model + 2;
uint32_t deviceId = (uint32_t) strtoul(code, nullptr, 10);
ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));
if (!cpu->name.length) {
ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC");
ffStrbufAppendS(&cpu->name, code);
}
} else {
ffStrbufSetS(&cpu->name, model);
}
} else if (ffStrStartsWith(model, "sc")) {
const char* code = model + 2;
uint32_t deviceId = (uint32_t) strtoul(code, nullptr, 10);
ffStrbufSetStatic(&cpu->name, ffCPUQualcommCodeToName(deviceId));
if (!cpu->name.length) {
ffStrbufAppendS(&cpu->name, "Qualcomm Snapdragon SC");
ffStrbufAppendS(&cpu->name, code);
}
} else {
ffStrbufSetS(&cpu->name, model);
}
ffStrbufSetStatic(&cpu->vendor, "Qualcomm");
@@ -1040,6 +1104,10 @@ static const char* detectPhysicalCores(FFCPUResult* cpu) {
if (cpu->name.length) {
ffStrbufPrependS(&cpu->name, "Machine ");
}
#elif __sparc__ || __sparc
// Cpu0ClkTck is in Hz, printed as "%016lx" by sparc64 and "%ld" by sparc32. A 32-bit userland can run
// on a 64-bit kernel, so take the base from the width of the value rather than from our own bitness.
cpu->frequencyBase = (uint32_t) (strtoull(cpuMHz.chars, nullptr, cpuMHz.length == 16 ? 16 : 10) / 1000000);
#endif
}
+2 -1
View File
@@ -1,4 +1,5 @@
#include "cpu.h"
#include "common/endian.h"
#include "common/windows/registry.h"
#include "common/windows/nt.h"
#include "common/mallocHelper.h"
@@ -205,7 +206,7 @@ static const char* detectMaxSpeedBySmbios(FFCPUResult* cpu) {
}
}
uint32_t speed = data->MaxSpeed;
uint32_t speed = FF_READ_LE(data->MaxSpeed);
// Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base
if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2) {
return "Possible invalid CPU max speed in SMBIOS data. See #800";
+8 -5
View File
@@ -1,4 +1,5 @@
#include "cpucache.h"
#include "common/endian.h"
#include "common/smbios.h"
#include "common/strutil.h"
@@ -43,24 +44,26 @@ const char* ffDetectCPUCache(FFCPUCacheResult* result) {
continue;
}
bool enabled = !!(data->CacheConfiguration & (1 << 7));
uint16_t cacheConfiguration = FF_READ_LE(data->CacheConfiguration);
uint16_t installedSize = FF_READ_LE(data->InstalledSize);
bool enabled = !!(cacheConfiguration & (1 << 7));
if (!enabled) {
continue;
}
uint32_t size = data->InstalledSize;
uint32_t size = installedSize;
if (size == 0) {
continue;
}
if (data->InstalledSize != 0xFFFF) {
if (installedSize != 0xFFFF) {
size *= (size >> 15 ? 64 : 1) * 1024u;
} else if (data->Header.Length > offsetof(FFSmbiosCacheInfo, InstalledCacheSize2)) {
size = data->InstalledCacheSize2;
size = FF_READ_LE(data->InstalledCacheSize2);
size *= (size >> 31 ? 64 : 1) * 1024u;
}
uint32_t level = (data->CacheConfiguration & 0b111u) + 1;
uint32_t level = (cacheConfiguration & 0b111u) + 1;
FFCPUCacheType type;
switch (data->SystemCacheType) {
+70
View File
@@ -0,0 +1,70 @@
#include "cursor.h"
#include "common/apple/cf_helpers.h"
static bool appendColor(FFstrbuf* str, CFDictionaryRef dict) {
double r, g, b, a;
if (
ffCfDictGetDouble(dict, CFSTR("red"), &r) ||
ffCfDictGetDouble(dict, CFSTR("green"), &g) ||
ffCfDictGetDouble(dict, CFSTR("blue"), &b) ||
ffCfDictGetDouble(dict, CFSTR("alpha"), &a)) {
return false;
}
r = r * 255 + .5;
g = g * 255 + .5;
b = b * 255 + .5;
a = a * 255 + .5;
uint32_t color = ((uint32_t) r << 24) | ((uint32_t) g << 16) | ((uint32_t) b << 8) | ((uint32_t) a);
// clang-format off
switch (color)
{
case 0x000000FF: ffStrbufAppendS(str, "Black"); break;
case 0x0433FFFF: ffStrbufAppendS(str, "Blue"); break;
case 0xAA7942FF: ffStrbufAppendS(str, "Brown"); break;
case 0x00FDFFFF: ffStrbufAppendS(str, "Cyan"); break;
case 0x00F900FF: ffStrbufAppendS(str, "Green"); break;
case 0xFF40FFFF: ffStrbufAppendS(str, "Magenta"); break;
case 0xFF9300FF: ffStrbufAppendS(str, "Orange"); break;
case 0x942192FF: ffStrbufAppendS(str, "Purple"); break;
case 0xFF2600FF: ffStrbufAppendS(str, "Red"); break;
case 0xFFFB00FF: ffStrbufAppendS(str, "Yellow"); break;
case 0xFFFFFFFF: ffStrbufAppendS(str, "White"); break;
case 0x00000000: ffStrbufAppendS(str, "Transparent"); break;
default: ffStrbufAppendF(str, "#%08X", color); break;
}
// clang-format on
return true;
}
void ffDetectCursor(FFCursorResult* result) {
// Read via cfprefsd (equivalent to `defaults read com.apple.universalaccess <key>`)
ffStrbufAppendS(&result->theme, "Fill - ");
{
FF_CFTYPE_AUTO_RELEASE CFTypeRef color = CFPreferencesCopyAppValue(CFSTR("cursorFill"), CFSTR("com.apple.universalaccess"));
if (!color || CFGetTypeID(color) != CFDictionaryGetTypeID() || !appendColor(&result->theme, (CFDictionaryRef) color)) {
ffStrbufAppendS(&result->theme, "Black");
}
}
ffStrbufAppendS(&result->theme, ", Outline - ");
{
FF_CFTYPE_AUTO_RELEASE CFTypeRef color = CFPreferencesCopyAppValue(CFSTR("cursorOutline"), CFSTR("com.apple.universalaccess"));
if (!color || CFGetTypeID(color) != CFDictionaryGetTypeID() || !appendColor(&result->theme, (CFDictionaryRef) color)) {
ffStrbufAppendS(&result->theme, "White");
}
}
{
FF_CFTYPE_AUTO_RELEASE CFTypeRef mouseDriverCursorSize = CFPreferencesCopyAppValue(CFSTR("mouseDriverCursorSize"), CFSTR("com.apple.universalaccess"));
double size = 32;
if (mouseDriverCursorSize && ffCfNumGetDouble(mouseDriverCursorSize, &size) == nullptr) {
ffStrbufAppendUInt(&result->size, (uint32_t) (size * 32 + 0.5));
} else {
ffStrbufAppendS(&result->size, "32");
}
}
}
-63
View File
@@ -1,63 +0,0 @@
#include "cursor.h"
#import <Foundation/Foundation.h>
static void appendColor(FFstrbuf* str, NSDictionary* dict)
{
uint32_t r = (uint32_t) (((NSNumber*) dict[@"red"]).doubleValue * 255 + .5);
uint32_t g = (uint32_t) (((NSNumber*) dict[@"green"]).doubleValue * 255 + .5);
uint32_t b = (uint32_t) (((NSNumber*) dict[@"blue"]).doubleValue * 255 + .5);
uint32_t a = (uint32_t) (((NSNumber*) dict[@"alpha"]).doubleValue * 255 + .5);
uint32_t color = (r << 24) | (g << 16) | (b << 8) | a;
switch (color)
{
case 0x000000FF: ffStrbufAppendS(str, "Black"); return;
case 0x0433FFFF: ffStrbufAppendS(str, "Blue"); return;
case 0xAA7942FF: ffStrbufAppendS(str, "Brown"); return;
case 0x00FDFFFF: ffStrbufAppendS(str, "Cyan"); return;
case 0x00F900FF: ffStrbufAppendS(str, "Green"); return;
case 0xFF40FFFF: ffStrbufAppendS(str, "Magenta"); return;
case 0xFF9300FF: ffStrbufAppendS(str, "Orange"); return;
case 0x942192FF: ffStrbufAppendS(str, "Purple"); return;
case 0xFF2600FF: ffStrbufAppendS(str, "Red"); return;
case 0xFFFB00FF: ffStrbufAppendS(str, "Yellow"); return;
case 0xFFFFFFFF: ffStrbufAppendS(str, "White"); return;
case 0x00000000: ffStrbufAppendS(str, "Transparent"); return;
default: ffStrbufAppendF(str, "#%08X", color); return;
}
}
void ffDetectCursor(FFCursorResult* result)
{
NSError* error;
NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/com.apple.universalaccess.plist", instance.state.platform.homeDir.chars];
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
error:&error];
if(error)
{
ffStrbufAppendS(&result->error, error.localizedDescription.UTF8String);
return;
}
NSDictionary* color;
ffStrbufAppendS(&result->theme, "Fill - ");
if ((color = dict[@"cursorFill"]))
appendColor(&result->theme, color);
else
ffStrbufAppendS(&result->theme, "Black");
ffStrbufAppendS(&result->theme, ", Outline - ");
if ((color = dict[@"cursorOutline"]))
appendColor(&result->theme, color);
else
ffStrbufAppendS(&result->theme, "White");
NSNumber* mouseDriverCursorSize = dict[@"mouseDriverCursorSize"];
if (mouseDriverCursorSize)
ffStrbufAppendF(&result->size, "%d", (int) (mouseDriverCursorSize.doubleValue * 32 + 0.5));
else
ffStrbufAppendS(&result->size, "32");
}
+2 -19
View File
@@ -11,29 +11,12 @@
#include "detection/displayserver/displayserver.h"
#include <ctype.h>
#ifdef __FreeBSD__
#include <paths.h>
#ifndef _PATH_LOCALBASE
#define _PATH_LOCALBASE "/usr/local"
#endif
#elif __OpenBSD__
#define _PATH_LOCALBASE "/usr/local"
#elif __NetBSD__
#define _PATH_LOCALBASE "/usr/pkg"
#endif
static void getKDE(FFstrbuf* result, [[maybe_unused]] FFDEOptions* options) {
#ifdef _PATH_LOCALBASE
ffParsePropFile(_PATH_LOCALBASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
ffParsePropFile(FF_PATH_PKG_BASE "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
if (result->length == 0) {
ffParsePropFile(_PATH_LOCALBASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
ffParsePropFile(FF_PATH_PKG_BASE "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
}
#else
ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/wayland-sessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
if (result->length == 0) {
ffParsePropFile(FASTFETCH_TARGET_DIR_USR "/share/xsessions/plasmax11.desktop", "X-KDE-PluginInfo-Version =", result);
}
#endif
if (result->length == 0) {
ffParsePropFileData("xsessions/plasma.desktop", "X-KDE-PluginInfo-Version =", result);
+1 -2
View File
@@ -72,8 +72,7 @@ const char* ffDetectDiskIO(FFlist* result, FFDiskIOOptions* options) {
uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
uint64_t temp = *currValue;
*currValue -= *prevValue;
*currValue /= (time2 - time1) / 1000 /* seconds */;
*currValue = (*currValue - *prevValue) * 1000 / (time2 - time1); // Calculate per second
// For next function call
*prevValue = temp;
@@ -174,6 +174,11 @@ static bool detectDE(FFDisplayServerResult* ds) {
ffStrbufPrependS(&ds->dePrettyName, "OxygenOS");
return true;
}
if (ffSettingsGetAndroidProperty("ro.product.brand", &ds->dePrettyName) && ffStrbufEqualS(&ds->dePrettyName, "asus")) {
ffStrbufClear(&ds->dePrettyName);
ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName);
return true;
}
if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName)) {
if (ffStrbufStartsWithS(&ds->dePrettyName, "RedMagicOS")) {
ffStrbufInsertNC(&ds->dePrettyName, strlen("RedMagicOS"), 1, ' ');
@@ -187,13 +192,23 @@ static bool detectDE(FFDisplayServerResult* ds) {
}
void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
const char* error = ffdsConnectXcbRandr(ds);
if (error) {
error = ffdsConnectXrandr(ds);
}
if (!error) {
ffdsDetectWMDE(ds);
return;
// Keep the same display-server preference as Linux: Wayland provides the
// richest output information, followed by XCB and XRandR.
if (instance.config.general.dsForceDrm == FF_DS_FORCE_DRM_TYPE_FALSE) {
ffdsConnectWayland(ds);
if (ds->displays.length == 0) {
ffdsConnectXcbRandr(ds);
}
if (ds->displays.length == 0) {
ffdsConnectXrandr(ds);
}
if (ds->displays.length > 0) {
ffdsDetectWMDE(ds);
return;
}
}
// https://source.android.com/docs/core/graphics/surfaceflinger-windowmanager
+57 -37
View File
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#if __FreeBSD__
#include <sys/sysctl.h>
@@ -15,7 +16,6 @@
#elif __OpenBSD__
#include <sys/param.h>
#include <sys/sysctl.h>
#include <kvm.h>
#elif __sun
#include <procfs.h>
#elif __NetBSD__
@@ -310,25 +310,33 @@ static const char* getFromProcesses(FFDisplayServerResult* result) {
}
}
#elif __OpenBSD__
kvm_t* kd = kvm_open(nullptr, nullptr, nullptr, KVM_NO_FILES, nullptr);
int count = 0;
const struct kinfo_proc* proc = kvm_getprocs(kd, KERN_PROC_UID, (int) userId, sizeof(*proc), &count);
if (proc) {
for (int i = 0; i < count; ++i) {
if (result->dePrettyName.length == 0) {
applyPrettyNameIfDE(result, proc[i].p_comm);
}
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, (int) userId, (int) sizeof(struct kinfo_proc), 0 };
size_t length = 0;
if (result->wmPrettyName.length == 0) {
applyNameIfWM(result, proc[i].p_comm);
}
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
}
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
break;
}
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
}
int count = (int) (length / sizeof(struct kinfo_proc));
for (int i = 0; i < count; ++i) {
if (result->dePrettyName.length == 0) {
applyPrettyNameIfDE(result, procs[i].p_comm);
}
if (result->wmPrettyName.length == 0) {
applyNameIfWM(result, procs[i].p_comm);
}
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
break;
}
}
kvm_close(kd);
#elif __sun
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
if (procdir == nullptr) {
@@ -376,48 +384,60 @@ static const char* getFromProcesses(FFDisplayServerResult* result) {
}
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
ffStrbufAppendS(&procPath, "/proc/");
uint32_t procPathLength = procPath.length;
FF_STRBUF_AUTO_DESTROY loginuid = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY processName = ffStrbufCreateA(256); // Some processes have large command lines (looking at you chrome)
int procfd = dirfd(procdir);
struct dirent* dirent;
while ((dirent = readdir(procdir)) != nullptr) {
// Match only folders starting with a number (the pid folders)
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
if ((dirent->d_type != DT_DIR && dirent->d_type != DT_UNKNOWN) || !ffCharIsDigit(dirent->d_name[0])) {
continue;
}
ffStrbufAppendS(&procPath, dirent->d_name);
ffStrbufSetS(&procPath, dirent->d_name);
uint32_t procFolderPathLength = procPath.length;
// Don't check for processes not owned by the current user.
char loginuid[32];
ffStrbufAppendS(&procPath, "/loginuid");
ffReadFileBuffer(procPath.chars, &loginuid);
if (ffStrbufToUInt(&loginuid, (uint64_t) -1) != userId) {
ffStrbufSubstrBefore(&procPath, procPathLength);
ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
if (bytesRead <= 0) {
continue;
}
loginuid[bytesRead] = '\0';
if (strtol(loginuid, nullptr, 10) != (long) userId) {
continue;
}
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
// We check the cmdline for the process name, because it is not trimmed.
ffStrbufAppendS(&procPath, "/cmdline");
ffReadFileBuffer(procPath.chars, &processName);
ffStrbufTrimRightSpace(&processName);
ffStrbufSubstrBeforeFirstC(&processName, '\0'); // Trim the arguments
ffStrbufSubstrAfterLastC(&processName, '/');
ffStrbufSubstrBefore(&procPath, procPathLength);
// We check the realpath for the process name
ffStrbufAppendS(&procPath, "/exe");
char exePath[PATH_MAX];
ssize_t exePathLength = readlinkat(procfd, procPath.chars, exePath, sizeof(exePath) - 1);
const char* processName;
if (exePathLength >= 0) {
exePath[exePathLength] = '\0';
processName = basename(exePath);
} else {
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
ffStrbufAppendS(&procPath, "/comm");
exePathLength = ffReadFileDataRelative(procfd, procPath.chars, sizeof(exePath) - 1, exePath);
if (exePathLength <= 0) {
continue;
}
if (exePath[exePathLength - 1] == '\n') {
--exePathLength;
}
exePath[exePathLength] = '\0';
processName = exePath;
}
if (result->dePrettyName.length == 0) {
applyPrettyNameIfDE(result, processName.chars);
applyPrettyNameIfDE(result, processName);
}
if (result->wmPrettyName.length == 0) {
applyNameIfWM(result, processName.chars);
applyNameIfWM(result, processName);
}
if (result->dePrettyName.length > 0 && result->wmPrettyName.length > 0) {
+1 -1
View File
@@ -331,7 +331,7 @@ static bool xcbRandrHandleMonitors(XcbRandrData* data, xcb_screen_t* screen) {
}
static void xcbRandrHandleScreen(XcbRandrData* data, xcb_screen_t* screen) {
// With all the initialisation done, start the detection
// With all the initialization done, start the detection
if (xcbRandrHandleMonitors(data, screen)) {
return;
}
+58 -64
View File
@@ -14,74 +14,68 @@
#define MTLFeatureSet_macOS_GPUFamily2_v1 ((MTLFeatureSet) 10005)
#endif
const char* ffGpuDetectDriverVersion(FFlist* gpus)
{
if (@available(macOS 10.7, *))
{
NSMutableArray* arr = NSMutableArray.new;
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
[arr addObject:@(x->driver.chars)];
NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)arr, (__bridge CFArrayRef)@[@"CFBundleVersion"]));
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
{
NSString* version = dict[@(x->driver.chars)][@"CFBundleVersion"];
if (version)
{
ffStrbufAppendC(&x->driver, ' ');
ffStrbufAppendS(&x->driver, version.UTF8String);
}
}
return nullptr;
const char* ffGpuDetectDriverVersion(FFlist* gpus) {
NSMutableArray* arr = NSMutableArray.new;
FF_LIST_FOR_EACH (FFGPUResult, x, *gpus) {
[arr addObject:@(x->driver.chars)];
}
return "Unsupported macOS version";
NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef) arr, (__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
FF_LIST_FOR_EACH (FFGPUResult, x, *gpus) {
NSString* version = dict[@(x->driver.chars)][@"CFBundleVersion"];
if (version) {
ffStrbufAppendC(&x->driver, ' ');
ffStrbufAppendS(&x->driver, version.UTF8String);
}
}
return nullptr;
}
const char* ffGpuDetectMetal(FFlist* gpus)
{
if (@available(macOS 10.13, *))
{
for (id<MTLDevice> device in MTLCopyAllDevices())
{
FFGPUResult* gpu = nullptr;
FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
{
if (x->deviceId == device.registryID)
{
gpu = x;
break;
}
const char* ffGpuDetectMetal(FFlist* gpus) {
// Metal.framework is statically linked into the application,
// so `MTLCopyAllDevices` is always available, or the application will not start.
for (id<MTLDevice> device in MTLCopyAllDevices()) {
FFGPUResult* gpu = nullptr;
FF_LIST_FOR_EACH (FFGPUResult, x, *gpus) {
if (x->deviceId == device.registryID) {
gpu = x;
break;
}
if (!gpu) continue;
#ifndef MAC_OS_X_VERSION_10_15
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1])
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 2");
else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1");
#else // MAC_OS_X_VERSION_10_15
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
if ([device supportsFamily:MTLGPUFamilyMetal4])
ffStrbufSetStatic(&gpu->platformApi, "Metal 4");
else if ([device supportsFamily:MTLGPUFamilyMetal3])
ffStrbufSetStatic(&gpu->platformApi, "Metal 3");
#pragma clang diagnostic pop
else if ([device supportsFamily:MTLGPUFamilyCommon3])
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3");
else if ([device supportsFamily:MTLGPUFamilyCommon2])
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2");
else if ([device supportsFamily:MTLGPUFamilyCommon1])
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1");
gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
gpu->index = (uint32_t) device.locationNumber;
if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0)
gpu->shared.total = device.recommendedMaxWorkingSetSize;
#endif
}
return nullptr;
if (!gpu) {
continue;
}
#ifndef MAC_OS_X_VERSION_10_15
if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 2");
} else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1");
}
#else // MAC_OS_X_VERSION_10_15
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
if ([device supportsFamily:MTLGPUFamilyMetal4]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal 4");
} else if ([device supportsFamily:MTLGPUFamilyMetal3]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal 3");
}
#pragma clang diagnostic pop
else if ([device supportsFamily:MTLGPUFamilyCommon3]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3");
} else if ([device supportsFamily:MTLGPUFamilyCommon2]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2");
} else if ([device supportsFamily:MTLGPUFamilyCommon1]) {
ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1");
}
gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
gpu->index = (uint32_t) device.locationNumber;
if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0) {
gpu->shared.total = device.recommendedMaxWorkingSetSize;
}
#endif
}
return "Metal API is not supported by this macOS version";
return nullptr;
}
+1 -1
View File
@@ -426,7 +426,7 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf
ffStrbufAppendS(deviceDir, "/revision");
if (ffReadFileBuffer(deviceDir->chars, buffer)) {
char* pend;
uint64_t revision = strtoul(buffer->chars, &pend, 16);
uint32_t revision = (uint32_t) strtoul(buffer->chars, &pend, 16);
if (pend != buffer->chars) {
ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu);
}
+3 -14
View File
@@ -1,21 +1,10 @@
#include "gpu.h"
#include "common/io.h"
#include "common/path.h"
#include "common/properties.h"
#include "common/memrchr.h"
#include "common/strutil.h"
#include <stdlib.h>
#ifdef __FreeBSD__
#include <paths.h>
#ifndef _PATH_LOCALBASE
#define _PATH_LOCALBASE "/usr/local"
#endif
#elif __OpenBSD__
#define _PATH_LOCALBASE "/usr/local"
#elif __NetBSD__
#define _PATH_LOCALBASE "/usr/pkg"
#endif
#if FF_HAVE_EMBEDDED_PCIIDS
#include "fastfetch_pciids.c.inc"
#endif
@@ -46,9 +35,9 @@ static const FFstrbuf* loadPciIds() {
}
}
#elif __OpenBSD__ || __FreeBSD__ || __NetBSD__
ffReadFileBuffer(_PATH_LOCALBASE "/share/hwdata/pci.ids", &pciids);
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/hwdata/pci.ids", &pciids);
if (pciids.length == 0) {
ffReadFileBuffer(_PATH_LOCALBASE "/share/pciids/pci.ids", &pciids);
ffReadFileBuffer(FF_PATH_PKG_BASE "/share/pciids/pci.ids", &pciids);
}
#elif __sun
ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids);
+1 -1
View File
@@ -19,7 +19,7 @@ static int walkDevTree(di_node_t node, [[maybe_unused]] di_minor_t minor, FFlist
gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
gpu->type = FF_GPU_TYPE_UNKNOWN;
gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
gpu->deviceId = strtoul(di_bus_addr(node), nullptr, 16);
gpu->deviceId = (uint64_t) strtoull(di_bus_addr(node), nullptr, 16);
gpu->frequency = FF_GPU_FREQUENCY_UNSET;
gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
+56 -43
View File
@@ -62,50 +62,63 @@ static void detectGTKFromSettings(FFGTKResult* result) {
const FFDisplayServerResult* wmde = ffConnectDisplayServer();
if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4)) {
themeName = ffSettingsGetXFConf("xsettings", "/Net/ThemeName", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetXFConf("xsettings", "/Net/IconThemeName", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetXFConf("xsettings", "/Gtk/FontName", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeName", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeSize", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetXFConfFirstMatch("xfce4-desktop", "/backdrop/screen0", FF_VARIANT_TYPE_STRING, nullptr, testXfconfWallpaperPropKey).strValue;
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON)) {
themeName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/gtk-theme", "org.cinnamon.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/icon-theme", "org.cinnamon.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/font-name", "org.cinnamon.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-theme", "org.cinnamon.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-size", "org.cinnamon.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetGnome("/org/cinnamon/desktop/background/picture-uri", "org.cinnamon.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE)) {
themeName = ffSettingsGetGnome("/org/mate/interface/gtk-theme", "org.mate.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetGnome("/org/mate/interface/icon-theme", "org.mate.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetGnome("/org/mate/interface/font-name", "org.mate.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-theme", "org.mate.peripherals-mouse", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-size", "org.mate.peripherals-mouse", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetGnome("/org/mate/desktop/background", "org.mate.background", nullptr, "picture-filename", FF_VARIANT_TYPE_STRING).strValue;
} else if (
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) {
themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
} else if (
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) {
ffEnlightenmentSettings settings = {};
if (ffSettingsGetEnlightenmentProperty(&settings)) {
themeName = settings.theme;
iconsName = settings.icon_theme;
fontName = settings.font;
cursorTheme = settings.use_e_cursor ? "Enlightenment" : "Application";
cursorSize = settings.cursor_size;
wallpaper = settings.desktop_default_background;
if (wmde->dePrettyName.length > 0) {
if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_XFCE4)) {
themeName = ffSettingsGetXFConf("xsettings", "/Net/ThemeName", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetXFConf("xsettings", "/Net/IconThemeName", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetXFConf("xsettings", "/Gtk/FontName", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeName", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetXFConf("xsettings", "/Gtk/CursorThemeSize", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetXFConfFirstMatch("xfce4-desktop", "/backdrop/screen0", FF_VARIANT_TYPE_STRING, nullptr, testXfconfWallpaperPropKey).strValue;
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_CINNAMON)) {
themeName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/gtk-theme", "org.cinnamon.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/icon-theme", "org.cinnamon.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetGnome("/org/cinnamon/desktop/interface/font-name", "org.cinnamon.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-theme", "org.cinnamon.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetGnome("/org/cinnamon/desktop/interface/cursor-size", "org.cinnamon.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetGnome("/org/cinnamon/desktop/background/picture-uri", "org.cinnamon.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
} else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_MATE)) {
themeName = ffSettingsGetGnome("/org/mate/interface/gtk-theme", "org.mate.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetGnome("/org/mate/interface/icon-theme", "org.mate.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetGnome("/org/mate/interface/font-name", "org.mate.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-theme", "org.mate.peripherals-mouse", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetGnome("/org/mate/peripherals-mouse/cursor-size", "org.mate.peripherals-mouse", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetGnome("/org/mate/desktop/background", "org.mate.background", nullptr, "picture-filename", FF_VARIANT_TYPE_STRING).strValue;
} else if (
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_GNOME_CLASSIC) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_UNITY) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_BUDGIE) ||
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_NEBIDE)) {
themeName = ffSettingsGetGnome("/org/gnome/desktop/interface/gtk-theme", "org.gnome.desktop.interface", nullptr, "gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetGnome("/org/gnome/desktop/interface/icon-theme", "org.gnome.desktop.interface", nullptr, "icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetGnome("/org/gnome/desktop/interface/font-name", "org.gnome.desktop.interface", nullptr, "font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-theme", "org.gnome.desktop.interface", nullptr, "cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetGnome("/org/gnome/desktop/interface/cursor-size", "org.gnome.desktop.interface", nullptr, "cursor-size", FF_VARIANT_TYPE_INT).intValue;
wallpaper = ffSettingsGetGnome("/org/gnome/desktop/background/picture-uri", "org.gnome.desktop.background", nullptr, "picture-uri", FF_VARIANT_TYPE_STRING).strValue;
} else if (
ffStrbufIgnCaseEqualS(&wmde->dePrettyName, FF_DE_PRETTY_ENLIGHTENMENT)) {
ffEnlightenmentSettings settings = {};
if (ffSettingsGetEnlightenmentProperty(&settings)) {
themeName = settings.theme;
iconsName = settings.icon_theme;
fontName = settings.font;
cursorTheme = settings.use_e_cursor ? "Enlightenment" : "Application";
cursorSize = settings.cursor_size;
wallpaper = settings.desktop_default_background;
}
}
} else {
// Standalone WMs (Hyprland, sway, niri, i3, ...) report no DE and have no settings
// daemon, but GTK apps on them still read org.gnome.desktop.interface.
// Read via DConf, not GSettings: GSettings synthesizes the schema default for keys
// the user never set, DConf returns nothing. Runs last; applyGTKSettings() only
// fills still-empty fields, so config files always win.
themeName = ffSettingsGetDConf("/org/gnome/desktop/interface/gtk-theme", FF_VARIANT_TYPE_STRING).strValue;
iconsName = ffSettingsGetDConf("/org/gnome/desktop/interface/icon-theme", FF_VARIANT_TYPE_STRING).strValue;
fontName = ffSettingsGetDConf("/org/gnome/desktop/interface/font-name", FF_VARIANT_TYPE_STRING).strValue;
cursorTheme = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-theme", FF_VARIANT_TYPE_STRING).strValue;
cursorSize = ffSettingsGetDConf("/org/gnome/desktop/interface/cursor-size", FF_VARIANT_TYPE_INT).intValue;
}
applyGTKSettings(result, themeName, iconsName, fontName, cursorTheme, cursorSize, wallpaper);
+9 -1
View File
@@ -1,4 +1,5 @@
#include "host.h"
#include "common/endian.h"
#include "common/smbios.h"
typedef struct [[gnu::packed]] FFSmbiosSystemInfo {
@@ -52,7 +53,14 @@ const char* ffDetectHost(FFHostResult* host) {
static_assert(offsetof(FFSmbiosSystemInfo, UUID) == 0x08, "FFSmbiosSystemInfo.UUID offset is wrong");
if (data->Header.Length > offsetof(FFSmbiosSystemInfo, UUID)) {
ffStrbufSetF(&host->uuid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", data->UUID.TimeLow, data->UUID.TimeMid, data->UUID.TimeHighAndVersion, data->UUID.ClockSeqHiAndReserved, data->UUID.ClockSeqLow, data->UUID.Node[0], data->UUID.Node[1], data->UUID.Node[2], data->UUID.Node[3], data->UUID.Node[4], data->UUID.Node[5]);
ffStrbufSetF(&host->uuid, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
FF_READ_LE(data->UUID.TimeLow),
FF_READ_LE(data->UUID.TimeMid),
FF_READ_LE(data->UUID.TimeHighAndVersion),
data->UUID.ClockSeqHiAndReserved,
data->UUID.ClockSeqLow,
data->UUID.Node[0], data->UUID.Node[1], data->UUID.Node[2],
data->UUID.Node[3], data->UUID.Node[4], data->UUID.Node[5]);
}
static_assert(offsetof(FFSmbiosSystemInfo, SKUNumber) == 0x19, "FFSmbiosSystemInfo.SKUNumber offset is wrong");
+55 -22
View File
@@ -7,32 +7,65 @@
#include <winternl.h>
#include <wchar.h>
static bool fillResult(FFInitSystemResult* result, uint32_t ppid, uint32_t pid, uint16_t len, PCWSTR name) {
if (ppid != 4 /* System */ || len <= 4 || _wcsnicmp(name + len - 4, L".exe", 4) != 0) { // smss.exe
return false;
}
result->pid = pid;
// We have no permission to open the process for querying the full information
wchar_t exePath[MAX_PATH];
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, name);
ffGetFileVersion(exePath, NULL, &result->version);
ffStrbufSetWS(&result->exe, exePath);
ffStrbufSetNWS(&result->name, len - 4, name);
return true;
}
const char* ffDetectInitSystem(FFInitSystemResult* result) {
// We only need to find the first user process, so 1024 entries should be enough
SYSTEM_PROCESS_INFORMATION buffer[1024] = {};
ULONG size = sizeof(buffer);
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size);
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
return "NtQuerySystemInformation(SystemProcessInformation) failed";
if (ffIsSystemBasicProcessInfoAvailable()) {
// SYSTEM_BASICPROCESS_INFORMATION entries are much smaller than SYSTEM_PROCESS_INFORMATION ones,
// so a modest buffer should be enough to contain all processes
SYSTEM_BASICPROCESS_INFORMATION buffer[1024];
NTSTATUS status = NtQuerySystemInformation(SystemBasicProcessInformation, buffer, sizeof(buffer), NULL);
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
goto fallback;
}
for (auto ptr = buffer; ;ptr = (PSYSTEM_BASICPROCESS_INFORMATION) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId,
(uint32_t)(uintptr_t) ptr->UniqueProcessId,
ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer),
ptr->ImageName.Buffer)) {
return nullptr;
}
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
if (!ptr->NextEntryOffset) {
return "Could not find init system process";
}
}
}
for (SYSTEM_PROCESS_INFORMATION* ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer);
if (ptr->InheritedFromUniqueProcessId == (HANDLE)(uintptr_t) 4 /* System */ &&
len > 4 && _wcsnicmp(ptr->ImageName.Buffer + len - 4, L".exe", 4) == 0) { // smss.exe
result->pid = (uint32_t)(uintptr_t) ptr->UniqueProcessId;
// We have no permission to open the process for querying the full information
wchar_t exePath[MAX_PATH];
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, ptr->ImageName.Buffer);
ffGetFileVersion(exePath, NULL, &result->version);
ffStrbufSetWS(&result->exe, exePath);
ffStrbufSetNWS(&result->name, len - 4, ptr->ImageName.Buffer);
return nullptr;
fallback:
{
// We only need to find the first user process, so 1024 entries should be enough
SYSTEM_PROCESS_INFORMATION buffer[1024] = {};
ULONG size = sizeof(buffer);
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size);
if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) {
return "NtQuerySystemInformation(SystemProcessInformation) failed";
}
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
if (!ptr->NextEntryOffset) {
return "Could not find init system process";
for (auto ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) {
assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer));
uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer);
if (fillResult(result, (uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId,
(uint32_t)(uintptr_t) ptr->UniqueProcessId, len, ptr->ImageName.Buffer)) {
return nullptr;
}
// The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated.
if (!ptr->NextEntryOffset) {
return "Could not find init system process";
}
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
typedef struct FFLMResult {
FFstrbuf service;
FFstrbuf type;
FFstrbuf prettyName;
FFstrbuf version;
} FFLMResult;
+18
View File
@@ -0,0 +1,18 @@
#include "lm.h"
#import <Foundation/Foundation.h>
const char* ffDetectLM(FFLMResult* result) {
ffStrbufSetStatic(&result->service, "loginwindow");
ffStrbufSetStatic(&result->prettyName, "Login Window");
NSError* error;
NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@"/System/Library/CoreServices/loginwindow.app/Contents/Info.plist" isDirectory:NO]
error:&error];
if (dict) {
ffStrbufSetS(&result->version, ((NSString*) dict[@"CFBundleShortVersionString"]).UTF8String);
}
return nullptr;
}
+368 -26
View File
@@ -1,18 +1,34 @@
#include "lm.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include "common/properties.h"
#include "common/dbus.h"
#include "common/processing.h"
#include "detection/displayserver/displayserver.h"
#include "common/strutil.h"
#ifdef FF_HAVE_DBUS
#include "common/dbus.h"
#endif
#include <unistd.h>
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
#if __FreeBSD__
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#elif __OpenBSD__
#include <sys/param.h>
#include <sys/sysctl.h>
#elif __sun
#include <procfs.h>
#elif __NetBSD__
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
static const char* getGdmVersion(FFstrbuf* version) {
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
const char* error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
if (error || version->length == 0) {
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm3", "--version", nullptr });
error = ffProcessAppendStdOut(version, (char* const[]) { "gdm", "--version", nullptr });
if (error || version->length == 0) {
return "Failed to get GDM version";
}
@@ -37,6 +53,8 @@ static const char* getSshdVersion(FFstrbuf* version) {
#ifdef FF_HAVE_ZLIB
#include "common/library.h"
#include "common/path.h"
#include <stdlib.h>
#include <zlib.h>
@@ -49,7 +67,7 @@ static const char* getSddmVersion(FFstrbuf* version) {
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose)
gzFile file = ffgzopen(FASTFETCH_TARGET_DIR_USR "/share/man/man1/sddm.1.gz", "rb");
gzFile file = ffgzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb");
if (file == Z_NULL) {
return "ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed";
}
@@ -112,35 +130,368 @@ static const char* getLightdmVersion(FFstrbuf* version) {
return nullptr;
}
const char* ffDetectLM(FFLMResult* result) {
#if __linux__ && !__ANDROID__
#ifdef FF_HAVE_DBUS
static const char* detectBySystemdDbus(FFLMResult* result) {
// This is the standard way to query the systemd session service name,
// and requires no $XDG_SESSION_ID being available
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
if (ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus) != nullptr) {
return "Failed to load system DBus";
}
if (!ffDBusGetPropertyString(
&dbus,
"org.freedesktop.login1",
"/org/freedesktop/login1/session/auto",
"org.freedesktop.login1.Session",
"Service",
&result->service)) {
return "Failed to get systemd session Service property";
}
return nullptr;
}
#endif
#define FF_SYSTEMD_SESSIONS_PATH "/run/systemd/sessions/"
#define FF_SYSTEMD_USERS_PATH "/run/systemd/users/"
static const char* detectBySystemdPrivate(FFLMResult* result) {
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY sessionId = ffStrbufCreateS(getenv("XDG_SESSION_ID"));
if (sessionId.length == 0) {
// On some incorrectly configured systems, $XDG_SESSION_ID is not set. Try finding it ourself
// WARNING: This is private data. Do not parse
ffStrbufSetF(&path, FF_SYSTEMD_USERS_PATH "%d", instance.state.platform.uid);
ffStrbufAppendS(&path, FF_SYSTEMD_USERS_PATH);
ffStrbufAppendUInt(&path, instance.state.platform.uid);
// This is actually buggy, and assumes current user is using DE
// `sd_pid_get_session` can be a better option, but we need to find a pid to use
if (!ffParsePropFileValues(path.chars, 1, (FFpropquery[]) {
{ "DISPLAY=", &sessionId },
})) {
if (!ffParsePropFile(path.chars, "DISPLAY=", &sessionId)) {
return "Failed to get $XDG_SESSION_ID";
}
}
ffStrbufSetS(&path, FF_SYSTEMD_SESSIONS_PATH);
ffStrbufClear(&path);
ffStrbufAppendS(&path, FF_SYSTEMD_SESSIONS_PATH);
ffStrbufAppend(&path, &sessionId);
// WARNING: This is private data. Do not parse
if (!ffParsePropFileValues(path.chars, 2, (FFpropquery[]) {
{ "SERVICE=", &result->service },
{ "TYPE=", &result->type },
})) {
if (!ffParsePropFile(path.chars, "SERVICE=", &result->service)) {
return "Failed to parse " FF_SYSTEMD_SESSIONS_PATH "$XDG_SESSION_ID";
}
return nullptr;
}
#endif
static const char* testLms(const char* psName) {
static const char* A[] = {
"atrium",
};
static const char* C[] = {
"cdm",
};
static const char* E[] = {
"entrance",
};
static const char* G[] = {
"gdm",
"gdm3",
"greetd",
};
static const char* L[] = {
"lemurs",
"lightdm",
"lxdm",
"ly",
};
static const char* P[] = {
"plasmalogin",
};
static const char* S[] = {
"sddm",
"slim",
};
static const char* T[] = {
"tbsm",
};
static const char* X[] = {
"xdm",
"xenodm",
};
switch (psName[0]) {
#define PS_CASE(letter, array) \
case letter: \
for (uint32_t i = 0; i < ARRAY_SIZE(array); ++i) { \
if (ffStrEqualsIgnCase(psName, array[i])) { \
return array[i]; \
} \
} \
break;
PS_CASE('a', A)
PS_CASE('c', C)
PS_CASE('e', E)
PS_CASE('g', G)
PS_CASE('l', L)
PS_CASE('p', P)
PS_CASE('s', S)
PS_CASE('t', T)
PS_CASE('x', X)
}
return nullptr;
}
const char* detectByProcesses(FFLMResult* result) {
#if __FreeBSD__
#ifdef __DragonFly__
#define ki_comm kp_comm
#endif
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0 };
size_t length = 0;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
}
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
}
length /= sizeof(*procs);
for (struct kinfo_proc* proc = procs; proc < procs + length; ++proc) {
const char* lm = testLms(proc->ki_comm);
if (lm) {
ffStrbufSetStatic(&result->service, lm);
break;
}
}
#elif __OpenBSD__
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, 0, (int) sizeof(struct kinfo_proc), 0 };
size_t length = 0;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, nullptr) failed";
}
FF_AUTO_FREE struct kinfo_proc* procs = (struct kinfo_proc*) malloc(length);
request[5] = (int) (length / sizeof(struct kinfo_proc)); // count must be non-zero for data fetch
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_UID}, procs) failed";
}
int count = (int) (length / sizeof(struct kinfo_proc));
for (int i = 0; i < count; ++i) {
const char* lm = testLms(procs[i].p_comm);
if (lm) {
ffStrbufSetStatic(&result->service, lm);
break;
}
}
#elif __sun
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
if (procdir == nullptr) {
return "opendir(\"/proc\") failed";
}
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
ffStrbufAppendS(&procPath, "/proc/");
uint32_t procPathLength = procPath.length;
struct dirent* dirent;
while ((dirent = readdir(procdir)) != nullptr) {
if (!ffCharIsDigit(dirent->d_name[0])) {
continue;
}
ffStrbufAppendS(&procPath, dirent->d_name);
ffStrbufAppendS(&procPath, "/psinfo");
psinfo_t proc;
if (ffReadFileData(procPath.chars, sizeof(proc), &proc) == sizeof(proc)) {
ffStrbufSubstrBefore(&procPath, procPathLength);
if (proc.pr_uid != 0) {
continue;
}
const char* lm = testLms(proc.pr_fname);
if (lm) {
ffStrbufSetS(&result->service, lm);
break;
}
}
}
#elif __linux__ || __GNU__
FF_AUTO_CLOSE_DIR DIR* procdir = opendir("/proc");
if (procdir == nullptr) {
return "opendir(\"/proc\") failed";
}
FF_STRBUF_AUTO_DESTROY procPath = ffStrbufCreateA(64);
int procfd = dirfd(procdir);
struct dirent* dirent;
while ((dirent = readdir(procdir)) != nullptr) {
// Match only folders starting with a number (the pid folders)
if (dirent->d_type != DT_DIR || !ffCharIsDigit(dirent->d_name[0])) {
continue;
}
ffStrbufSetS(&procPath, dirent->d_name);
uint32_t procFolderPathLength = procPath.length;
// Don't check for processes not owned by root (login managers run as root).
char loginuid[32];
ffStrbufAppendS(&procPath, "/loginuid");
ssize_t bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(loginuid) - 1, loginuid);
if (bytesRead <= 0) {
continue;
}
loginuid[bytesRead] = '\0';
if (strtol(loginuid, nullptr, 10) != (long) (uint32_t) -1 /* no loginuid */) {
continue;
}
ffStrbufSubstrBefore(&procPath, procFolderPathLength);
ffStrbufAppendS(&procPath, "/comm");
char comm[256];
bytesRead = ffReadFileDataRelative(procfd, procPath.chars, sizeof(comm) - 1, comm);
if (bytesRead <= 0) {
continue;
}
if (comm[bytesRead - 1] == '\n') {
--bytesRead;
}
comm[bytesRead] = '\0';
const char* lm = testLms(comm);
if (lm) {
ffStrbufSetS(&result->service, lm);
break;
}
}
#elif __NetBSD__
int request[] = { CTL_KERN, KERN_PROC2, KERN_PROC_UID, 0, sizeof(struct kinfo_proc2), INT_MAX };
size_t size = 0;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &size, nullptr, 0) != 0) {
return "sysctl(KERN_PROC_UID, nullptr) failed";
}
FF_AUTO_FREE struct kinfo_proc2* procs = malloc(size);
if (sysctl(request, ARRAY_SIZE(request), procs, &size, nullptr, 0) != 0) {
return "sysctl(KERN_PROC_UID, procs) failed";
}
for (struct kinfo_proc2* proc = procs; proc < procs + (size / sizeof(struct kinfo_proc2)); proc++) {
const char* lm = testLms(proc->p_comm);
if (lm) {
ffStrbufSetStatic(&result->service, lm);
break;
}
}
#endif
if (result->service.length == 0) {
return "Failed to detect login manager by processes";
}
return nullptr;
}
const char* ffDetectLM(FFLMResult* result) {
const char* error = "";
#if __linux__
#ifdef FF_HAVE_DBUS
error = detectBySystemdDbus(result);
#endif
if (error != nullptr) {
error = detectBySystemdPrivate(result);
}
#endif
if (error != nullptr) {
error = detectByProcesses(result);
}
if (error != nullptr) {
return error;
}
switch (result->service.chars[0]) {
case 'a':
if (ffStrEqualsIgnCase(result->service.chars, "atrium")) {
ffStrbufSetStatic(&result->prettyName, "Atrium");
}
break;
case 'c':
if (ffStrEqualsIgnCase(result->service.chars, "cdm")) {
ffStrbufSetStatic(&result->prettyName, "CDM");
}
break;
case 'e':
if (ffStrEqualsIgnCase(result->service.chars, "entrance")) {
ffStrbufSetStatic(&result->prettyName, "Entrance");
}
break;
case 'g':
if (ffStrbufStartsWithIgnCaseS(&result->service, "gdm")) {
ffStrbufSetStatic(&result->prettyName, "GDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "greetd")) {
ffStrbufSetStatic(&result->prettyName, "greetd");
}
break;
case 'l':
if (ffStrEqualsIgnCase(result->service.chars, "lemurs")) {
ffStrbufSetStatic(&result->prettyName, "Lemurs");
} else if (ffStrEqualsIgnCase(result->service.chars, "lightdm")) {
ffStrbufSetStatic(&result->prettyName, "LightDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "lxdm")) {
ffStrbufSetStatic(&result->prettyName, "LXDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "ly")) {
ffStrbufSetStatic(&result->prettyName, "Ly");
}
break;
case 'p':
if (ffStrEqualsIgnCase(result->service.chars, "plasmalogin")) {
ffStrbufSetStatic(&result->prettyName, "Plasma Login");
}
break;
case 's':
if (ffStrEqualsIgnCase(result->service.chars, "sddm")) {
ffStrbufSetStatic(&result->prettyName, "SDDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "slim")) {
ffStrbufSetStatic(&result->prettyName, "SLiM");
}
break;
case 't':
if (ffStrEqualsIgnCase(result->service.chars, "tbsm")) {
ffStrbufSetStatic(&result->prettyName, "TBSM");
}
break;
case 'x':
if (ffStrEqualsIgnCase(result->service.chars, "xdm")) {
ffStrbufSetStatic(&result->prettyName, "XDM");
} else if (ffStrEqualsIgnCase(result->service.chars, "xenodm")) {
ffStrbufSetStatic(&result->prettyName, "Xenodm");
}
break;
}
if (result->prettyName.length == 0) {
ffStrbufSet(&result->prettyName, &result->service);
}
if (instance.config.general.detectVersion) {
if (ffStrbufStartsWithS(&result->service, "gdm")) {
getGdmVersion(&result->version);
@@ -155,14 +506,5 @@ const char* ffDetectLM(FFLMResult* result) {
}
}
// Correct char cases
if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_WAYLAND)) {
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_WAYLAND);
} else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_X11)) {
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_X11);
} else if (ffStrbufIgnCaseEqualS(&result->type, FF_WM_PROTOCOL_TTY)) {
ffStrbufSetS(&result->type, FF_WM_PROTOCOL_TTY);
}
return nullptr;
}
+19
View File
@@ -0,0 +1,19 @@
#include "lm.h"
#include "common/windows/version.h"
#include "common/windows/nt.h"
#include <wchar.h>
const char* ffDetectLM(FFLMResult* result) {
ffStrbufSetStatic(&result->service, "LogonUI");
ffStrbufSetStatic(&result->prettyName, "Logon User Interface");
if (instance.config.general.detectVersion) {
wchar_t exePath[MAX_PATH];
_snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%ls", (const wchar_t*) SharedUserData->NtSystemRoot, L"LogonUI.exe");
ffGetFileVersion(exePath, nullptr, &result->version);
}
return nullptr;
}
+5 -1
View File
@@ -1,6 +1,10 @@
#include "detection/loadavg/loadavg.h"
#include <sys/loadavg.h>
#if __has_include(<sys/loadavg.h>)
#include <sys/loadavg.h>
#else
#include <stdlib.h>
#endif
const char* ffDetectLoadavg(double result[3]) {
return getloadavg(result, 3) == 3 ? nullptr : "getloadavg() failed";
+73 -2
View File
@@ -1,16 +1,87 @@
#include "detection/locale/locale.h"
#include "common/windows/unicode.h"
#include <windows.h>
#include <winnls.h>
const char* ffDetectLocale(FFstrbuf* result) {
ffStrbufAppendS(result, getenv("LC_ALL"));
if (result->length > 0) {
return nullptr;
}
ffStrbufAppendS(result, getenv("LANG")); // Available in MSYS2 and Cygwin
if (result->length > 0) {
return nullptr;
}
wchar_t name[LOCALE_NAME_MAX_LENGTH];
int size = GetUserDefaultLocaleName(name, LOCALE_NAME_MAX_LENGTH);
if (size <= 1) { // including '\0'
return "GetUserDefaultLocaleName() failed";
}
ffStrbufAppendNWS(result, (uint32_t) size - 1, name);
if (result->length > 2 && result->chars[2] == '-') { // Windows uses '-' instead of '_'
result->chars[2] = '_';
}
ffStrbufSetNWS(result, (uint32_t) size - 1, name);
uint32_t codePage = instance.state.platform.initCP;
if (__builtin_expect(codePage != 0, true)) {
ffStrbufAppendC(result, '.');
switch (codePage) {
case CP_UTF8:
ffStrbufAppendS(result, "UTF-8");
break;
case CP_UTF7:
ffStrbufAppendS(result, "UTF-7");
break;
case 1200:
ffStrbufAppendS(result, "UTF-16LE");
break;
case 1201:
ffStrbufAppendS(result, "UTF-16BE");
break;
case 936:
ffStrbufAppendS(result, "GBK");
break;
case 54936:
ffStrbufAppendS(result, "GB18030");
break;
case 950:
ffStrbufAppendS(result, "BIG5");
break;
case 932:
ffStrbufAppendS(result, "Shift_JIS");
break;
case 949:
ffStrbufAppendS(result, "EUC-KR");
break;
case 20932:
ffStrbufAppendS(result, "EUC-JP");
break;
case 20866:
ffStrbufAppendS(result, "KOI8-R");
break;
case 21866:
ffStrbufAppendS(result, "KOI8-U");
break;
case 20127:
ffStrbufAppendS(result, "US-ASCII");
break;
case 874:
case 1250 ... 1258:
ffStrbufAppendS(result, "Windows-");
ffStrbufAppendUInt(result, codePage);
break;
default:
ffStrbufAppendS(result, "CP");
ffStrbufAppendUInt(result, codePage);
break;
}
}
return nullptr;
}
+19 -12
View File
@@ -1,19 +1,26 @@
#include "memory.h"
#include <windows.h>
#include "common/windows/nt.h"
const char* ffDetectMemory(FFMemoryResult* ram) {
MEMORYSTATUSEX statex = {
.dwLength = sizeof(statex),
};
// GlobalMemoryStatusEx() internally uses
// NtQuerySystemInformation(SystemBasicPerformanceInformation) in Win 7, and
// NtQuerySystemInformation(SystemMemoryUsageInformation) in Win 10
if (!GlobalMemoryStatusEx(&statex)) {
return "GlobalMemoryStatusEx() failed";
}
// Note: GlobalMemoryStatusEx() internally uses SystemMemoryUsageInformation in Win 10
ram->bytesTotal = statex.ullTotalPhys;
ram->bytesUsed = statex.ullTotalPhys - statex.ullAvailPhys;
SYSTEM_BASIC_PERFORMANCE_INFORMATION sbpi;
if (!NT_SUCCESS(NtQuerySystemInformation(SystemBasicPerformanceInformation, &sbpi, sizeof(sbpi), NULL)))
return "Failed to query memory information";
uint64_t phyPages =
#if _WIN64
SharedUserData->FullNumberOfPhysicalPages;
if (__builtin_expect(phyPages == 0, false)) { // Not supported on Windows 8.1
phyPages = SharedUserData->NumberOfPhysicalPages; // ULONG, maximum 16 TB
}
#else
SharedUserData->NumberOfPhysicalPages;
#endif
uint64_t pageSize = instance.state.platform.sysinfo.pageSize;
ram->bytesTotal = phyPages * pageSize;
ram->bytesUsed = (phyPages - sbpi.AvailablePages) * pageSize;
return nullptr;
}
+1 -2
View File
@@ -70,8 +70,7 @@ const char* ffDetectNetIO(FFlist* result, FFNetIOOptions* options) {
uint64_t* prevValue = (uint64_t*) ((uint8_t*) icPrev + off);
uint64_t* currValue = (uint64_t*) ((uint8_t*) icCurr + off);
uint64_t temp = *currValue;
*currValue -= *prevValue;
*currValue /= (time2 - time1) / 1000 /* seconds */;
*currValue = (*currValue - *prevValue) * 1000 / (time2 - time1); // Calculate per second
*prevValue = temp;
}
}
+32 -2
View File
@@ -8,9 +8,39 @@ void ffDetectOSImpl(FFOSResult* os) {
ffSettingsGetAndroidProperty("ro.build.version.release", &os->version);
ffSettingsGetAndroidProperty("ro.build.version.release", &os->versionID);
ffStrbufSet(&os->versionID, &os->version);
ffSettingsGetAndroidProperty("ro.build.version.codename", &os->codename);
unsigned major = 0;
for (const char* p = os->version.chars; *p >= '0' && *p <= '9'; ++p) {
major = major * 10 + (unsigned) (*p - '0');
}
const char* codename = NULL;
// https://en.wikipedia.org/wiki/Android_version_history
// Android 5 is the oldest version supported by Termux
// clang-format off
switch (major) {
case 17: codename = "Cinnamon Bun"; break;
case 16: codename = "Baklava"; break;
case 15: codename = "Vanilla Ice Cream"; break;
case 14: codename = "Upside Down Cake"; break;
case 13: codename = "Tiramisu"; break;
case 12: codename = "Snow Cone"; break;
case 11: codename = "Red Velvet Cake"; break;
case 10: codename = "Quince Tart"; break;
case 9: codename = "Pie"; break;
case 8: codename = "Oreo"; break;
case 7: codename = "Nougat"; break;
case 6: codename = "Marshmallow"; break;
case 5: codename = "Lollipop"; break;
}
// clang-format on
if (codename) {
ffStrbufSetStatic(&os->codename, codename);
} else {
ffSettingsGetAndroidProperty("ro.build.version.codename", &os->codename);
}
ffSettingsGetAndroidProperty("ro.build.id", &os->buildID);
}
+1
View File
@@ -12,6 +12,7 @@ typedef struct FFPackagesResult {
uint32_t brewCask;
uint32_t cards;
uint32_t choco;
uint32_t crux;
uint32_t dpkg;
uint32_t emerge;
uint32_t eopkg;
+3
View File
@@ -487,6 +487,9 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts,
if (FF_PACKAGES_IS_ENABLED(options, APK)) {
packageCounts->apk += getNumStrings(baseDir, "/lib/apk/db/installed", "C:Q", "apk");
}
if (FF_PACKAGES_IS_ENABLED(options, CRUX)) {
packageCounts->crux += getNumStrings(baseDir, "/var/lib/pkg/db", "\n\n", "crux");
}
if (FF_PACKAGES_IS_ENABLED(options, DPKG)) {
packageCounts->dpkg += getNumStrings(baseDir, "/var/lib/dpkg/status", "Status: install ok installed", "dpkg");
}
@@ -122,6 +122,7 @@ const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options)
}
#else
const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
FF_UNUSED(result, options);
return "Fastfetch was compiled without libgeom support";
}
#endif
@@ -34,7 +34,7 @@ static void parsePhysicalDisk(int dfd, const char* devName, FFPhysicalDiskOption
ssize_t fileSize = ffReadFileDataRelative(dfd, "size", ARRAY_SIZE(blkSize) - 1, blkSize);
if (fileSize > 0) {
blkSize[fileSize] = 0;
size = (uint64_t) strtoul(blkSize, nullptr, 10) * 512;
size = (uint64_t) strtoull(blkSize, nullptr, 10) * 512;
}
}
@@ -38,7 +38,7 @@ static void appendDevice(
if (size)
{
char* unit = nullptr;
device->size = strtoul(size.UTF8String, &unit, 10);
device->size = (uint64_t) strtoull(size.UTF8String, &unit, 10);
if (*unit == ' ') ++unit;
switch (*unit)
@@ -1,4 +1,5 @@
#include "physicalmemory.h"
#include "common/endian.h"
#include "common/smbios.h"
// 7.18
@@ -82,7 +83,8 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
}
const char* strings = (const char*) data + data->Header.Length;
bool installed = data->Size != 0;
uint16_t size = FF_READ_LE(data->Size);
bool installed = size != 0;
if (!installed && !options->showEmptySlots) {
continue;
@@ -101,19 +103,21 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
device->installed = installed;
device->ecc = false;
if (installed && data->TotalWidth != 0xFFFF && data->DataWidth != 0xFFFF) {
device->ecc = data->TotalWidth > data->DataWidth;
uint16_t totalWidth = FF_READ_LE(data->TotalWidth);
uint16_t dataWidth = FF_READ_LE(data->DataWidth);
if (installed && totalWidth != 0xFFFF && dataWidth != 0xFFFF) {
device->ecc = totalWidth > dataWidth;
}
if (installed && data->Size != 0xFFFF) {
if (data->Size == 0x7FFF) {
device->size = (data->ExtendedSize & ~(1ULL << 31)) * 1024ULL * 1024ULL;
} else if (data->Size & (1 << 15)) {
if (installed && size != 0xFFFF) {
if (size == 0x7FFF) {
device->size = (FF_READ_LE(data->ExtendedSize) & ~(1ULL << 31)) * 1024ULL * 1024ULL;
} else if (size & (1 << 15)) {
// in kB
device->size = (data->Size & ~(1ULL << 15)) * 1024ULL;
device->size = (size & ~(1ULL << 15)) * 1024ULL;
} else {
// in MB
device->size = data->Size * 1024ULL * 1024ULL;
device->size = size * 1024ULL * 1024ULL;
}
}
@@ -206,8 +210,9 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, Speed)) // 2.3+
{
if (data->Speed) {
device->maxSpeed = data->Speed == 0xFFFF ? data->ExtendedSpeed : data->Speed;
uint16_t speed = FF_READ_LE(data->Speed);
if (speed) {
device->maxSpeed = speed == 0xFFFF ? FF_READ_LE(data->ExtendedSpeed) : speed;
}
ffStrbufSetStatic(&device->vendor, ffSmbiosLocateString(strings, data->Manufacturer));
@@ -223,10 +228,11 @@ const char* ffDetectPhysicalMemory(FFPhysicalMemoryOptions* options, FFlist* res
if (installed && data->Header.Length > offsetof(FFSmbiosMemoryDevice, ConfiguredMemorySpeed)) // 2.7+
{
if (data->ConfiguredMemorySpeed) {
device->runningSpeed = data->ConfiguredMemorySpeed == 0xFFFF
? data->ExtendedConfiguredSpeed
: data->ConfiguredMemorySpeed;
uint16_t configuredSpeed = FF_READ_LE(data->ConfiguredMemorySpeed);
if (configuredSpeed) {
device->runningSpeed = configuredSpeed == 0xFFFF
? FF_READ_LE(data->ExtendedConfiguredSpeed)
: configuredSpeed;
}
}
}
+8 -1
View File
@@ -1,5 +1,12 @@
#pragma once
#include "fastfetch.h"
#include "modules/processes/option.h"
const char* ffDetectProcesses(uint32_t* result);
typedef struct FFProcessesResult
{
uint32_t processes;
uint32_t threads;
} FFProcessesResult;
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result);
+41
View File
@@ -0,0 +1,41 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
#import <libproc.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, nullptr}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* processes = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), processes, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL, processes}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &processes[i];
if (!options->countKprocs && (proc->kp_proc.p_flag & P_SYSTEM)) {
continue;
}
result->processes++;
pid_t pid = proc->kp_proc.p_pid;
struct proc_taskinfo taskInfo;
if (proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, sizeof(taskInfo)) == sizeof(taskInfo)) {
// NOTE: This fails for system processes
result->threads += (uint32_t) taskInfo.pti_threadnum;
}
}
return nullptr;
}
+23 -11
View File
@@ -1,23 +1,35 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/user.h>
#endif
#include <sys/types.h>
#include <sys/user.h>
#ifndef KERN_PROC_PROC
#define KERN_PROC_PROC KERN_PROC_ALL // Apple
#endif
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
*result = (uint32_t) (length / sizeof(struct kinfo_proc));
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* procs = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_PROC}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &procs[i];
if (!options->countKprocs && (proc->ki_flag & P_KPROC)) {
continue;
}
result->processes++;
result->threads += (uint32_t) proc->ki_numthreads;
}
return nullptr;
}
+38
View File
@@ -0,0 +1,38 @@
#include "processes.h"
#include "common/mallocHelper.h"
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/proc.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
int request[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL | (options->countKprocs ? KERN_PROC_FLAG_LWKT : 0) };
size_t length;
if (sysctl(request, ARRAY_SIZE(request), nullptr, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
// The process table may change between the two sysctl calls; retry with a larger buffer.
length += length / 8 + sizeof(struct kinfo_proc);
FF_AUTO_FREE struct kinfo_proc* procs = malloc(length);
if (sysctl(request, ARRAY_SIZE(request), procs, &length, nullptr, 0) != 0) {
return "sysctl({CTL_KERN, KERN_PROC, KERN_PROC_ALL}) failed";
}
uint32_t count = (uint32_t) (length / sizeof(struct kinfo_proc));
for (uint32_t i = 0; i < count; ++i) {
const struct kinfo_proc* proc = &procs[i];
if (proc->kp_pid == -1) {
// Kernel thread, only returned when countKprocs is set via KERN_PROC_FLAG_LWKT
result->threads++;
continue;
}
result->processes++;
result->threads += (uint32_t) proc->kp_nthreads;
}
return nullptr;
}
+44
View File
@@ -0,0 +1,44 @@
#include "processes.h"
#include <hurd.h>
#include <ps.h>
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
struct ps_context* context = nullptr;
if (ps_context_create(getproc(), &context) != 0) {
return "ps_context_create(getproc()) failed";
}
const char* error = nullptr;
struct proc_stat_list* list = nullptr;
if (proc_stat_list_create(context, &list) != 0) {
error = "proc_stat_list_create() failed";
goto done;
}
if (proc_stat_list_add_all(list, nullptr, nullptr) != 0) {
error = "proc_stat_list_add_all() failed";
goto done;
}
proc_stat_list_set_flags(list, PSTAT_NUM_THREADS);
for (uint32_t i = 0; i < list->num_procs; i++) {
struct proc_stat* stat = list->proc_stats[i];
if (!options->countKprocs && proc_stat_pid(stat) == 2) { // Kernel Task
continue;
}
++result->processes;
if (proc_stat_has(stat, PSTAT_NUM_THREADS)) {
result->threads += proc_stat_num_threads(stat);
}
}
done:
if (list) {
proc_stat_list_free(list);
}
ps_context_free(context);
return error;
}
+11 -2
View File
@@ -2,13 +2,22 @@
#include <OS.h>
const char* ffDetectProcesses(uint32_t* result) {
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
system_info info;
if (get_system_info(&info) != B_OK) {
return "Error getting system info";
}
*result = info.used_teams;
result->processes = info.used_teams;
result->threads = info.used_threads;
if (!options->countKprocs) {
team_info ti;
if (get_team_info(B_SYSTEM_TEAM, &ti) == B_OK) {
result->processes--;
result->threads -= (uint32_t) ti.thread_count;
}
}
return nullptr;
}
+56 -10
View File
@@ -1,27 +1,73 @@
#include "processes.h"
#include "common/io.h"
#include "common/memrchr.h"
#include "common/strutil.h"
const char* ffDetectProcesses(uint32_t* result) {
#define PF_KTHREAD 0x00200000 // defined in kernel include/linux/sched.h
const char* ffDetectProcesses(const FFProcessesOptions* options, FFProcessesResult* result) {
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/proc");
if (dir == nullptr) {
return "opendir(\"/proc\") failed";
}
uint32_t num = 0;
int procfd = dirfd(dir);
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (
#ifdef _DIRENT_HAVE_D_TYPE
(entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) &&
#endif
ffCharIsDigit(entry->d_name[0]))
++num;
}
if ((entry->d_type == DT_DIR || entry->d_type == DT_UNKNOWN) && ffCharIsDigit(entry->d_name[0])) {
char statPath[32];
strcpy(ffStrCopy(statPath, entry->d_name, sizeof(statPath)), "/stat");
*result = num;
char statBuffer[512];
ssize_t statLength = ffReadFileDataRelative(procfd, statPath, sizeof(statBuffer) - 1, statBuffer);
if (statLength < 0) {
continue;
}
statBuffer[statLength] = '\0';
const char* cursor = (const char*) memrchr(statBuffer, ')', (size_t) statLength);
if (!cursor) {
continue;
}
cursor += 2; // skip ") "
if (__builtin_expect(cursor >= statBuffer + statLength, false)) {
goto skip_process;
}
char* p;
for (uint32_t field = 3; field <= 20 /*num_threads*/; ++field) {
switch (field) {
case 9: // flags
if ((strtoul(cursor, &p, 10) & PF_KTHREAD) && !options->countKprocs) {
goto skip_process;
}
cursor = p;
break;
case 20: // num_threads
result->threads += (uint32_t) strtoul(cursor, &p, 10);
cursor = p;
break;
default:
while (*cursor != ' ' && __builtin_expect(*cursor != '\0', true)) {
++cursor;
}
break;
}
++cursor;
if (__builtin_expect(cursor >= statBuffer + statLength, false)) {
goto skip_process;
}
}
++result->processes;
}
skip_process:;
}
return nullptr;
}

Some files were not shown because too many files have changed in this diff Show More