Compare commits

..

302 Commits

Author SHA1 Message Date
dependabot[bot] 9fac492ac9 CI: Bump the github-actions group across 1 directory with 5 updates
Bumps the github-actions group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [uraimo/run-on-arch-action](https://github.com/uraimo/run-on-arch-action) | `3.2.0` | `3.2.1` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.1` | `4.38.2` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.1` | `4.38.2` |
| [vmactions/solaris-vm](https://github.com/vmactions/solaris-vm) | `1.4.0` | `1.4.1` |
| [msys2/setup-msys2](https://github.com/msys2/setup-msys2) | `2.32.0` | `2.33.0` |



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

Updates `github/codeql-action/init` from 4.38.1 to 4.38.2
- [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/1c5b675653bb5c22dbe9b12b556ec555138e09fd...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `github/codeql-action/analyze` from 4.38.1 to 4.38.2
- [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/1c5b675653bb5c22dbe9b12b556ec555138e09fd...2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2)

Updates `vmactions/solaris-vm` from 1.4.0 to 1.4.1
- [Release notes](https://github.com/vmactions/solaris-vm/releases)
- [Commits](https://github.com/vmactions/solaris-vm/compare/a11f407d3720a3717673d4194f19ee63b284977e...87d3c436511cef802cd1637f86f73b1a97715c8c)

Updates `msys2/setup-msys2` from 2.32.0 to 2.33.0
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/msys2/setup-msys2/compare/66cd2cce69caa17b53920067426061ca1de3a884...ec48f7c5447b3140e2b088413ae3a55687bccb6e)

---
updated-dependencies:
- dependency-name: uraimo/run-on-arch-action
  dependency-version: 3.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: vmactions/solaris-vm
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: msys2/setup-msys2
  dependency-version: 2.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-05 04:45:05 +00:00
Carter Li 1989c6f549 Codec (Linux): eliminates a compiler warning [ci skip] 2026-10-05 12:16:36 +08:00
Carter Li 6e2a25759d Networking: removes compression support 2026-10-04 17:36:05 +08:00
Carter Li 460a715901 Logo (Image): fixes a possible building failure 2026-10-04 12:40:35 +08:00
Carter Li 9ac3822105 CI: removes codespell 2026-10-03 18:47:22 +08:00
Carter Li e02e44f673 Global: fixes compiling 2026-10-03 18:39:47 +08:00
Carter Li 0f712b4eaf CMake (macOS): fixes a linker warning 2026-10-03 18:36:47 +08:00
Ashed 995d172d3e Global: Added Finnish support (#2633)
* Global: Added fi support

* Remove a random space i accidentally added

* Fix more spaces
2026-10-03 18:35:56 +08:00
Carter Li 8102478f2f Global: prefers gnu::cleanup 2026-10-03 18:09:17 +08:00
Carter Li 537ffac39c Chore: forces eol=lf 2026-10-03 18:09:17 +08:00
Carter Li 95bcc4da5b Common: adds more FF_DEBUG 2026-10-03 18:09:17 +08:00
Carter Li 1b3594f600 Global: adds more FF_DEBUG 2026-10-03 18:09:17 +08:00
Carter Li ae1288d087 Global (Windows): adds more FF_DEBUG 2026-10-03 12:29:52 +08:00
Carter Li b201fd2db9 Common: improves reliability of FF_DEBUG 2026-10-03 10:53:21 +08:00
Carter Li 7f458b03e8 CMake: moves no-pie config to CI configs 2026-10-03 08:32:49 +08:00
Carter Li 5f65515c3c CMake: reduces binary size 2026-10-03 01:36:12 +08:00
Carter Li 3d0747b56d Common (Windows): optimises ico generation 2026-10-03 01:34:55 +08:00
Carter Li 68c5dd31f5 Global: marks ffGenerate*JsonConfig as [[gnu::cold]] 2026-10-03 01:32:02 +08:00
Carter Li a57ebe992a Battery (Windows): eliminates a compiler warning [ci skip] 2026-10-02 23:40:59 +08:00
Carter Li 54dcb1b6f9 Wifi (SunOS): eliminates a maybe-uninitialized warning 2026-10-02 23:25:06 +08:00
dependabot[bot] 39d667d1d5 CI: Bump the github-actions group with 5 updates (#2623)
* CI: Bump the github-actions group with 5 updates

Bumps the github-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [benchmark-action/github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark) | `1.22.1` | `1.22.2` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.0` | `4.38.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.0` | `4.38.1` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `4.3.0` | `4.4.0` |
| [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) | `2.3` | `3.0` |


Updates `benchmark-action/github-action-benchmark` from 1.22.1 to 1.22.2
- [Release notes](https://github.com/benchmark-action/github-action-benchmark/releases)
- [Changelog](https://github.com/benchmark-action/github-action-benchmark/blob/master/CHANGELOG.md)
- [Commits](https://github.com/benchmark-action/github-action-benchmark/compare/52576c92bccf6ac60c8223ec7eb2565637cae9ba...4322e5726e6334590d251fc4f92bec0efafc45dc)

Updates `github/codeql-action/init` from 4.38.0 to 4.38.1
- [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/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd)

Updates `github/codeql-action/analyze` from 4.38.0 to 4.38.1
- [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/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd)

Updates `docker/setup-qemu-action` from 4.3.0 to 4.4.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/1f40c72289eff860ee54a304f1438e3cff362e0a...99012661954931238ded8c8b007157a8430204e1)

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

---
updated-dependencies:
- dependency-name: benchmark-action/github-action-benchmark
  dependency-version: 1.22.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.4.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: '3.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update build-windows-hosts.yml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Carter Li <CarterLi@users.noreply.github.com>
2026-10-02 23:18:48 +08:00
Carter Li 7099964530 Release: v2.70.0 2026-10-02 22:22:44 +08:00
Carter Li ae4cbcbdba Global: removes quickjs-ng completely 2026-10-02 22:16:51 +08:00
Carter Li cee27354ba Battery (Android): removes dumpsys path 2026-10-02 22:04:29 +08:00
Carter Li 109644ec77 Disk (Haiku): fixes /boot being removed by hideFolders 2026-10-02 21:18:20 +08:00
Carter Li 782b7b8fd1 Display (macOS): don't read CGDisplayModeRef's private layout 2026-10-02 20:08:01 +08:00
Kashie w a9bf53b4a1 Logo: add filled Artix logo to match filled Arch logo (#2631) 2026-10-02 20:07:21 +08:00
Carter Li ab200636f1 Battery (Android): adds temperature and remaining time detection 2026-10-01 20:59:13 +08:00
Carter Li 4d8d6a5fe9 Brightness (Linux): removes ddcutil dep 2026-09-30 10:36:27 +08:00
Carter Li ef853fe858 Brightness (Linux): adds a native impl 2026-09-30 10:35:55 +08:00
Carter Li 47fbfcb0e9 Media (Android): fixes the descriptors a media session reply delivered being closed from a reply that had already gone out of scope, which closed a descriptor that was not theirs and leaked the ones that were 2026-09-30 00:40:57 +08:00
Carter Li c88ec3bcc3 Android: improves performance of dex parsing 2026-09-29 23:58:44 +08:00
Carter Li 2838dccbfd Sound (Android): adds support via /dev/binder 2026-09-29 23:58:02 +08:00
Carter Li f0d8c996e6 Sound (Linux): uses raw pulseaudio protocol
Also removes libpulse dependency
2026-09-29 18:22:03 +08:00
Carter Li 6ff7925f45 Packages (macOS): prefers getdirentries64 2026-09-29 13:36:32 +08:00
Achill Gilgenast ffbf4ec7ac Logo (Builtin): rename postmarketos to nura (#2626)
We changed our os-release ID in https://gitlab.postmarketos.org/postmarketOS/pmaports/-/merge_requests/9638
2026-09-29 00:28:10 -05:00
Jami Kettunen a3f46d9742 CPU (Linux): make Snapdragon X1/X2 detection more correct (#2625)
Use the previously discussed soc_id which have public values in EDK2.

The so far used toplevel devicetree compatible values weren't as precise
and some logic (x1, x2p, x2e) simply was dead code.

Also drops the CPU test due to reading soc_id sysfs file, could be
adapted if still wanted around.
2026-09-28 21:06:21 -05:00
Carter Li 4fb57bb39b Global (Windows): eliminates SHGetKnownFolderPath 2026-09-28 23:02:34 +08:00
Carter Li cdbe8f54c6 TerminalFont (Linux): uses ffFontInitXft to parse foot terminal font
Fixes #2624
2026-09-28 09:13:16 +08:00
李通洲 80632920bb Doc: uses the temp domain of repology [ci skip] 2026-09-27 09:04:10 +08:00
李通洲 5246f149eb Doc: updates info about image logo on Windows 2026-09-27 09:03:44 +08:00
Joshua Rogers f00a021e27 Completion (Bash): fixes --config/-c path completion splitting on spaces (#2621) [ci skip]
Co-authored-by: Carter Li <CarterLi@users.noreply.github.com>
2026-09-27 09:00:11 +08:00
İhsan Deniz 676dbcfee2 Vulkan: fixes driver name concatenation on multi-GPU systems (#2620)
* Vulkan: fixes driver name concatenation on multi-GPU systems

applyDriverName appended to the result buffer without clearing it. When
a later device reported a higher API version, its driver name was
appended to the name of the earlier device instead of replacing it, as
the comment in detectVulkan intends.

* CHANGELOG: moves the #2615 entry to Unreleased

#2616 was opened after 2.69.0 had been released, but its entry was
written under 2.69.0 Bugfixes, so the changelog listed the fix as part
of 2.69.0.
2026-09-27 08:35:20 +08:00
İhsan Deniz 9801d89316 Brightness (Linux): fixes random built-in flag and stdout leak in DDC/CI detection (#2616)
DDC/CI results never set `builtin`, and FF_LIST_ADD does not zero the new
element, so external monitors were reported as built-in or external at
random. Set it to false, as the BSD DDC/CI backend does.

FF_SUPPRESS_IO() was declared inside the `if (ffddca_init)` block, so its
cleanup restored stdout before display detection ran and libddcutil trace
lines could reach the output, ahead of `--format json` too. Declare it at
function scope so it covers the whole detection, including the
FF_DISABLE_DLOPEN build.

Closes #2615
2026-09-26 18:18:43 +08:00
Carter Li fb52d32452 Packages (Linux): uses getdirent64(2) directly 2026-09-26 14:18:00 +08:00
李通洲 36dc82e926 Bootmgr: cleans code 2026-09-26 14:17:24 +08:00
李通洲 7fdd1d8e93 LocalIP / NetIO / DNS (Windows): improves performance
Fixes #2614
2026-09-26 14:17:24 +08:00
Carter Li 0c3b852bf7 Merge pull request #2613 from fastfetch-cli/dev
Release v2.69.0
2026-09-25 18:46:15 +08:00
李通洲 866c5e89c8 Global: fixes several issues found by AI 2026-09-25 17:28:44 +08:00
Carter Li a1edf7316c GPU: adds more debug log 2026-09-25 12:27:58 +08:00
Carter Li c3164d15e1 TerminalShell: hardens version detection code 2026-09-25 11:34:13 +08:00
Carter Li a2eda0090a Shell (Linux): supports more ksh variants 2026-09-25 11:33:46 +08:00
Carter Li d8797ae314 Shell (Windows): fixes possible buffer overflow 2026-09-25 11:32:56 +08:00
Carter Li 93175aea1d TerminalShell: clears version buffer if detection failed 2026-09-25 10:58:27 +08:00
Carter Li 537635cd9c Logo: adds option logo.position: auto 2026-09-25 09:51:34 +08:00
Carter Li c68bedd9bf CMake: adds a missing test 2026-09-25 08:46:41 +08:00
Carter Li 80a931a87f CPU (Linux): fixes Snapdragon X2 series model detection
Fixes #2611
2026-09-25 08:46:13 +08:00
李通洲 d223b04ad8 Media (Android): adds support 2026-09-25 01:55:23 +08:00
李通洲 0e04ebdf63 Wallpaper (Android): adds support 2026-09-24 23:42:27 +08:00
李通洲 bba45429ae Global: fixes issues found by AI 2026-09-24 21:27:51 +08:00
Carter Li 00ccd8be12 Doc: simplifies changelog 2026-09-24 17:44:48 +08:00
Carter Li 42e4fe3085 General: adds general.preload.lua option 2026-09-24 15:55:23 +08:00
Carter Li fb5622215b Common (Format): fixes lua error message trimming 2026-09-24 15:32:15 +08:00
Carter Li ccf1dcfa6d CMake: disables quickjs by default 2026-09-24 14:17:55 +08:00
Carter Li 0c6a9d6e98 CI: fixes build 2026-09-24 14:11:05 +08:00
Carter Li b6b5dabd54 Packages (Windows): disables winget again 2026-09-24 14:09:41 +08:00
Carter Li fdbaaea962 CMake (macOS): uses lazy_framework if applicate
It's said deprecated. Let's see if it still works.
2026-09-24 13:52:29 +08:00
Carter Li add2339c9c chore: removes doc/wiki
Github doesn't support it
2026-09-24 13:51:28 +08:00
Carter Li e1e2de0929 Gamepad (macOS): adds battery level detection support 2026-09-24 11:15:28 +08:00
Carter Li 59f0f533e7 Doc: removes outdated info [ci skip] 2026-09-24 10:11:59 +08:00
李通洲 28f4f6464b Common (Windows): simplifies winrt helper 2026-09-24 10:09:02 +08:00
李通洲 982313063d Gamepad (Windows): fixes battery level detection 2026-09-24 00:02:27 +08:00
Carter Li 1e7386788f Logo (Sixel): adds modification notice
for 3a9595e121
2026-09-23 23:00:00 +08:00
minnnjuuu 3a9595e121 Logo (Sixel): fixes allocator leak on output allocation failure (#2610) 2026-09-23 22:50:22 +08:00
Carter Li f8df95d146 Common (Lua): don't segfault when formating a null string
Fixes #2608
2026-09-23 22:29:39 +08:00
Carter Li 592ef68d60 Bluetooth (Linux): adds comments about LE device support of BlueZ 2026-09-23 18:04:46 +08:00
Carter Li 0bf6c8df74 Doc: adds wiki to docs as a submodule 2026-09-23 18:04:08 +08:00
Carter Li be15a0942c Chore: fixes building 2026-09-23 15:18:56 +08:00
Carter Li 4a97cc6825 Bluetooth: adds option showType 2026-09-23 15:11:06 +08:00
Carter Li a78d2a7737 Bluetooth (Windows / macOS): adds Bluetooth LE device detection 2026-09-23 14:02:08 +08:00
Carter Li 3930759007 Common (Windows): adds a WinRT helper 2026-09-23 13:33:35 +08:00
李通洲 e21530551d Common (Percent): adds ffRssiToSignalQuality 2026-09-23 13:33:35 +08:00
Carter Li c26bace3c2 Global: fixes a lot bugs found by AI 2026-09-22 22:09:26 +08:00
Carter Li fe49a9624e Host (macOS): adds new models 2026-09-22 09:47:45 +08:00
李通洲 7f3209181e Global: adds general cache layer 2026-09-22 00:45:40 +08:00
李通洲 2f74d6be4d Networking (Linux): fixes possible memleaks 2026-09-21 20:13:56 +08:00
李通洲 256c6a4257 PublicIP / Weather: fixes possible can only be used once due to internal limitations error 2026-09-21 20:08:11 +08:00
Carter Li aaa9153f5a CI (Windows): fixes build
Partially reverts f26795826c
2026-09-21 14:55:12 +08:00
dependabot[bot] f26795826c CI: Bump the github-actions group with 5 updates (#2598)
Bumps the github-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [cross-platform-actions/action](https://github.com/cross-platform-actions/action) | `1.5.0` | `1.6.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.9` | `4.38.0` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.9` | `4.38.0` |
| [vmactions/solaris-vm](https://github.com/vmactions/solaris-vm) | `1.3.9` | `1.4.0` |
| [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) | `2.3` | `3.0` |


Updates `cross-platform-actions/action` from 1.5.0 to 1.6.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/faa0c6197e94aacf1c5956460152c8380d3560a5...e0b9770014ba65d5e0815f15b74031c3a635f641)

Updates `github/codeql-action/init` from 4.37.9 to 4.38.0
- [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/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63)

Updates `github/codeql-action/analyze` from 4.37.9 to 4.38.0
- [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/cdf488f595d80d6e07e03d4674febd5ab45fa938...b96794f015dfd88f77b49b1c93e0fa7110f94c63)

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

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

---
updated-dependencies:
- dependency-name: cross-platform-actions/action
  dependency-version: 1.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: vmactions/solaris-vm
  dependency-version: 1.4.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: '3.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
  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-21 01:37:15 -05:00
Carter Li 18b31ca875 Doc: fixes json_schema 2026-09-21 14:29:10 +08:00
Carter Li 17b98007b4 Logo (Image): improves compatibility for iTerm when printing gif logo 2026-09-21 14:26:29 +08:00
Carter Li 1023388d60 Doc: adds missing command flags 2026-09-21 14:22:14 +08:00
Carter Li 6842ae0757 Common (Format): fixes module format strings mishandling a value that carries more than one leading ANSI escape sequence 2026-09-21 14:22:14 +08:00
Carter Li b726ee5a85 Chore: fixes a comment [ci skip] 2026-09-21 14:22:14 +08:00
Piotr Kubaj fd08fdb9df Global (FreeBSD): fixes build when libc defines memrchr as a macro (#2599)
FreeBSD 16.0-CURRENT's <string.h> now provides C23-style
qualifier-preserving wrappers for the string search functions.  Besides
the prototype, `memrchr` (like `memchr`, `strchr`, `strstr`, `memmem`,
...) is also defined as a function-like macro built on `_Generic`:

    #define memrchr(b, c, n) __qualsel((b), \
        (const void *)(memrchr)((b), (c), (n)), (memrchr)((b), (c), (n)))

`common/memrchr.h` redeclares the function unconditionally.  When
<string.h> has already been included, that declaration is expanded by
the macro and every translation unit fails with:

    src/common/memrchr.h:11:7: error: expected identifier or '('
       11 | void* memrchr(const void* s, int c, size_t n);

A libc that defines `memrchr` as a macro has necessarily declared the
function it wraps, so skip our declaration in that case.  Nothing
changes on platforms where `memrchr` is not a macro, and the fallback
implementation is still selected by `check_function_exists`.
2026-09-21 01:21:57 -05:00
Natsu 203ccb31fa Packages (Linux): detects rum overlay packages (#2597) 2026-09-21 00:30:25 -05:00
Carter Li 3503facd68 Doc: update changelog [ci skip] 2026-09-21 09:43:09 +08:00
Carter Li b2d48a1908 JsonSchema: updates moduleFormat 2026-09-21 09:25:35 +08:00
Carter Li b803d1ac82 Logo (Image): tidy 2026-09-21 09:25:35 +08:00
李通洲 eda38a37ba Battery (Android): adds debug output 2026-09-21 00:01:45 +08:00
李通洲 0b76d33f07 Wifi(Android): fixes errors when location permission is not granted
Security detection support is also added
2026-09-20 23:49:39 +08:00
李通洲 39564acde4 Platform: replaces pageSize for pageSizeShift 2026-09-20 20:56:42 +08:00
李通洲 3bfc4ec674 Terminal (Windows): uses $SSH_TTY if available 2026-09-20 19:23:35 +08:00
李通洲 fea55b276e Terminal (Windows): ignores services; fixes ssh env detection 2026-09-20 16:25:52 +08:00
李通洲 bc51f1ba0c Brightness (Windows): fixes debug messages 2026-09-20 16:25:14 +08:00
李通洲 38e4ba290d OS (Windows): adds comments 2026-09-20 14:21:25 +08:00
李通洲 24fb73e9b4 CI: eliminates codespell errors 2026-09-20 14:21:03 +08:00
李通洲 f96a290f3b Packages (Windows): caches winget packages count; enables winget by default 2026-09-20 13:05:09 +08:00
李通洲 67da836a27 Top: reduces waitTime to 250ms 2026-09-20 11:24:25 +08:00
李通洲 15e4bc998a Global (Android): code cleanup 2026-09-20 09:23:32 +08:00
李通洲 f9f368c1fb Battery (Android): uses dumpsys if available 2026-09-20 08:13:11 +08:00
李通洲 4a33a2c410 Player (Windows): improves player name detection 2026-09-20 00:50:01 +08:00
李通洲 34639b61b3 Presets: updates examples/25 [ci skip]
Fixes #2595
2026-09-19 18:55:30 +08:00
Carter Li efbfda9cf8 Doc: updates changelog [ci skip] 2026-09-19 18:34:32 +08:00
Carter Li 6142077656 Chore: eliminates a compiler warning 2026-09-19 18:34:32 +08:00
李通洲 58692adabc Common (Networking): fixes a non-pure function marked as pure
Fixes CI
2026-09-19 07:58:59 +08:00
李通洲 665c472482 Wifi (Android): rewrites impl via /dev/binder 2026-09-19 00:39:25 +08:00
李通洲 2594c01fc1 Common: renames androidApi.h for android/api.h 2026-09-18 20:25:46 +08:00
李通洲 e1d7360453 Display (Android): adds richer information and adds detectWithGetprop back 2026-09-18 20:22:16 +08:00
李通洲 873a881dab Battery (Android): rewrites impl via /dev/binder 2026-09-18 20:22:16 +08:00
李通洲 ecbf218d6e Chore: fixes compiling errors 2026-09-18 15:01:11 +08:00
李通洲 2fb7fabc4d CI (Haiku): don't reboot after installing deps 2026-09-18 15:01:11 +08:00
李通洲 811e634dc8 Display (Android): adds real support via /system/bin/cmd 2026-09-18 15:01:10 +08:00
李通洲 098b501d0e Camera (Android): uses NDK instead of the unstable termux-api 2026-09-18 15:01:10 +08:00
李通洲 7baaae166f DE (Android): improves Android ROM detection
Fixes #2541
2026-09-18 15:01:10 +08:00
Ian Ribeiro 560a9f419d WM (Linux): supports Umbriel version detection (#2594) 2026-09-18 11:42:19 +08:00
李通洲 70d2dd37a6 Image (Logo): uses native image decoder on Android
Codec (Android): uses `availability` feature test
2026-09-18 01:17:28 +08:00
李通洲 97ac94eb44 Common: adds more gcc constrants 2026-09-17 21:00:36 +08:00
李通洲 6ff7f2772b Tests: adds more UTs 2026-09-17 20:11:25 +08:00
李通洲 f8fd708423 Global: addresses Copilot's review comments 2026-09-17 19:19:25 +08:00
Carter Li c23c98a85a Logo (Image): supports transparent background of sixel logos 2026-09-17 16:39:19 +08:00
Carter Li 43988a8d41 Release: v2.69.0 2026-09-17 14:37:17 +08:00
Carter Li 1b7cc1cd30 CPU (Linux): adds SoC name of MacBook Neo 2026-09-17 14:02:29 +08:00
Carter Li 59e33150dd CPU (Android): updates the marketing name of Dimensity 9600 Pro 2026-09-17 14:01:13 +08:00
Carter Li e004c97ee0 CMake: adds option -DENABLE_IMAGE_LOGO=<BOOL> 2026-09-17 13:50:07 +08:00
李通洲 9bac273165 NetIO / DiskIO: reduces waitTime & improves accurency 2026-09-17 00:33:47 +08:00
李通洲 d1b747192f Logo (Image): adds animation (gif / apng) support 2026-09-16 20:48:02 +08:00
李通洲 b96d7e7bec Networking: supports chunked response & custom port number 2026-09-16 14:49:50 +08:00
李通洲 7b28ad07d7 Chore: updates gitignore 2026-09-15 22:41:47 +08:00
李通洲 02dc6a4428 Modules: adds * to format args which are available in key format 2026-09-15 22:24:18 +08:00
李通洲 53e868667a Logo (Image): fixes a copy-paste error 2026-09-15 20:11:36 +08:00
Carter Li fbf79ac6fe FFstrbuf: improves empty-string handling
for ffStrbufMatchSeparatedNS and ffStrbufSeparatedContainNS

Fixes #2585
2026-09-15 10:43:25 +08:00
Carter Li 416715a505 Presets: fixes neofetch keys
Fixes #2588
2026-09-15 10:43:25 +08:00
Carter Li ff3360e036 Logo (Image): fixes potencial memleaks 2026-09-15 08:50:47 +08:00
Carter Li 98ec0a674c IO: increases FF_IO_TERM_RESP_WAIT_MS 2026-09-15 08:45:48 +08:00
李通洲 6178256404 Logo (Icat): adds logo-padding support 2026-09-15 00:44:02 +08:00
李通洲 129bb7309e Logo (Icat): adds --stdin=no so icat doesn't try to read stdin 2026-09-15 00:13:06 +08:00
李通洲 464adb3540 Logo (Image): migrates recache to cache: bool|regen 2026-09-14 23:45:28 +08:00
李通洲 3d7ea720fb Logo (Image): improves caching logic; takes mtime into account 2026-09-14 22:43:19 +08:00
李通洲 a90ae0249d IO (Windows): prefers unicode APIs 2026-09-14 22:42:16 +08:00
李通洲 375d0c6b25 Common (IO): adds ffPathGetMtime and uses it 2026-09-14 22:21:49 +08:00
李通洲 e3b3b5dfc6 Packages: fixes a copy-paste bug 2026-09-14 19:25:10 +08:00
Carter Li 35ffdb3995 Logo (Image): drops IM in favor of ImageIO on macOS 2026-09-14 19:02:50 +08:00
李通洲 e8350a0d6e Logo (Image): drops IM in favor of WIC on Windows 2026-09-14 19:02:50 +08:00
李通洲 98e9b6eef1 3rdparty: embeds libsixel 2026-09-14 19:02:50 +08:00
dependabot[bot] d796945604 CI: Bump docker/setup-qemu-action in the github-actions group (#2583)
Bumps the github-actions group with 1 update: [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action).


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

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  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-14 18:49:44 +08:00
Carter Li cd5a2def9a DE (Linux): uses COSMIC_VERSION if available
https://github.com/pop-os/cosmic-comp/issues/2425#issuecomment-5652401962
2026-09-13 17:33:15 +08:00
Carter Li 8334035927 Logo: small optimizations 2026-09-13 12:27:17 +08:00
Carter Li 1ee8528f10 Format: makes {#title} and {#key} honors brightColor config 2026-09-13 12:26:57 +08:00
Carter Li ced49ab2a4 Chore: dont let clang-format break my code style 2026-09-13 12:05:07 +08:00
Carter Li ec8b9be354 Packages: opens SQLite DB in immutable mode
.. to avoid `SQLITE_READONLY` errors
2026-09-13 11:13:33 +08:00
李通洲 19725cd2f7 Packages (Windows): adds --source winget to improve performance of winget package detection 2026-09-13 08:39:45 +08:00
李通洲 20c155e5b4 TerminalFont (Windows): honors JSON fragment
Fixes #2573
2026-09-13 08:39:45 +08:00
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
613 changed files with 39276 additions and 6248 deletions
-5
View File
@@ -1,5 +0,0 @@
[codespell]
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
+13
View File
@@ -1,2 +1,15 @@
# Normalise text files to LF in the repository and check them out with LF, so a CRLF
# never enters an object and never shows up as a whole-file diff on Windows.
* text=auto eol=lf
# The vendored AMD Display Library SDK ships with CRLF endings of its own. Leaving it
# alone keeps `Dep (3rdparty): upgrades display-library` from carrying a 7000-line
# whitespace diff, and a re-vendor will not fight the rule above.
src/3rdparty/display-library/** -text
# The logo fixtures store the exact bytes fastfetch prints, ANSI escapes included.
# They are demo data rather than text, and are not read by any test.
*.fflogo -text
*.h linguist-language=c
*.c linguist-language=c
+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@4322e5726e6334590d251fc4f92bec0efafc45dc # v1.22.2
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
+2 -2
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@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
with:
operating_system: dragonflybsd
version: '6.4.2'
@@ -36,7 +36,7 @@ jobs:
sudo pkg install -y llvm cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa libelf lua54 libva libvdpau
- name: CMake configuration
run: env CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
run: env CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+2 -2
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@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
with:
operating_system: freebsd
version: '15.1'
@@ -37,7 +37,7 @@ jobs:
sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3 egl opencl ocl-icd v4l_compat chafa lua54 libva libvdpau
- name: CMake configuration
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+2 -3
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@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
with:
operating_system: haiku
version: 'r1beta5'
version: 'r1beta6'
shell: bash
cpu_count: 4
@@ -29,7 +29,6 @@ jobs:
run: uname -a
- name: Install dependencies
shell: cpa.sh {0} --reboot
run: pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake llvm22_clang ninja pkgconfig python3.10 lua || pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake llvm22_clang ninja pkgconfig python3.10 lua
- name: CMake configuration
+3 -3
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@fa1f3e7de95534497266c7265950b5188355fbfb # v3.2.1
id: runcmd
with:
arch: armv7
@@ -30,8 +30,8 @@ jobs:
curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
echo -e 'Acquire::https::Verify-Peer "false";\nAcquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99ignore-certificates
apt-get update && apt-get install -y cmake make 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 libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr .
apt-get update && apt-get install -y cmake make 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 libdrm-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
cmake --build . --target package --verbose -j4
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+33 -5
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 libdrm-dev libelf-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
- name: install linuxbrew packages
run: |
@@ -52,19 +52,19 @@ jobs:
- name: Initialize CodeQL
if: inputs.arch == 'amd64'
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
languages: c
- 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 -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 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: build project
run: cmake --build . --target package --verbose -j4
- name: perform CodeQL analysis
if: inputs.arch == 'amd64'
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
- 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
+2 -2
View File
@@ -29,7 +29,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 gcc-13-multilib 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 gcc-13-multilib 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 libdrm-dev libelf-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
- name: install linuxbrew packages
run: |
@@ -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 -fno-pie" -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 -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: build project
run: cmake --build . --target package --verbose -j4
+3 -3
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: set up QEMU for loong64
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0
- name: build in loong64 container
run: |
@@ -37,13 +37,13 @@ jobs:
libdconf-dev libdbus-1-dev libmagickcore-dev \
libsqlite3-dev librpm-dev \
libegl-dev libglx-dev ocl-icd-opencl-dev \
libpulse-dev libdrm-dev \
libdrm-dev \
libelf-dev libefl-all-dev \
liblua5.4-dev \
libvdpau-dev libva-dev \
rpm
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr .
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
cmake --build . --target package --verbose -j$(nproc)
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+3 -3
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@fa1f3e7de95534497266c7265950b5188355fbfb # v3.2.1
id: runcmd
with:
arch: ${{ inputs.arch }}
@@ -31,8 +31,8 @@ jobs:
uname -a
apt-get update && apt-get install -y software-properties-common
add-apt-repository -y ppa:ubuntu-toolchain-r/test
apt-get update && apt-get install -y cmake make 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 libchafa-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr .
apt-get update && apt-get install -y cmake make 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 libdrm-dev libchafa-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
cmake --build . --target package --verbose -j4
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+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
+2 -2
View File
@@ -24,12 +24,12 @@ jobs:
run: |
cat /etc/alpine-release
uname -a
apk add cmake samurai vulkan-loader-dev libxcb-dev libxrandr-dev rpm-dev wayland-dev libdrm-dev dconf-dev imagemagick-dev chafa-dev zlib-dev dbus-dev mesa-dev opencl-dev sqlite-dev networkmanager-dev pulseaudio-dev ddcutil-dev elfutils-dev lua5.4-dev quickjs-ng-dev libvdpau-dev libva-dev gcc g++
apk add cmake samurai vulkan-loader-dev libxcb-dev libxrandr-dev rpm-dev wayland-dev libdrm-dev dconf-dev imagemagick-dev chafa-dev zlib-dev dbus-dev mesa-dev opencl-dev sqlite-dev networkmanager-dev elfutils-dev lua5.4-dev quickjs-ng-dev libvdpau-dev libva-dev gcc g++
shell: alpine.sh --root {0}
- name: build
run: |
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DIS_MUSL=ON -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -GNinja .
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DIS_MUSL=ON -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" -GNinja .
cmake --build . --target package --verbose -j4
shell: alpine.sh {0}
+2 -2
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@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
with:
operating_system: netbsd
version: '10.1'
@@ -35,7 +35,7 @@ jobs:
sudo pkgin -y install clang cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick libva libvdpau
- name: CMake configuration
run: CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
run: CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
run: uname -a
- name: configure project
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_SQLITE3=OFF -DENABLE_RPM=OFF -DENABLE_IMAGEMAGICK7=OFF -DENABLE_IMAGEMAGICK6=OFF -DENABLE_CHAFA=OFF -DENABLE_ZLIB=OFF -DENABLE_EGL=OFF -DENABLE_GLX=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_PULSE=OFF -DENABLE_DDCUTIL=OFF -DENABLE_ELF=OFF -DENABLE_EET=OFF -DENABLE_THREADS=OFF
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_SQLITE3=OFF -DENABLE_RPM=OFF -DENABLE_IMAGEMAGICK7=OFF -DENABLE_IMAGEMAGICK6=OFF -DENABLE_CHAFA=OFF -DENABLE_ZLIB=OFF -DENABLE_EGL=OFF -DENABLE_GLX=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_ELF=OFF -DENABLE_EET=OFF -DENABLE_THREADS=OFF -DENABLE_IMAGE_LOGO=OFF
- name: build project
run: cmake --build . --target package --verbose -j4
+2 -2
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@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
with:
operating_system: omnios
version: 'r151058'
@@ -35,7 +35,7 @@ jobs:
sudo pkg install gcc14 cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja
- name: CMake configuration
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: CMake build
run: cmake --build . --verbose -j4
+2 -2
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@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
with:
operating_system: openbsd
version: '7.9'
@@ -35,7 +35,7 @@ jobs:
sudo pkg_add -r llvm-21.1.8p4 cmake git wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 imagemagick chafa lua-5.4.8p0 # pkg-config is preinstalled
- name: CMake configuration
run: CC=clang-21 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
run: CC=clang-21 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+2 -2
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@87d3c436511cef802cd1637f86f73b1a97715c8c # v1.4.1
with:
usesh: true
envs: 'CMAKE_BUILD_TYPE'
@@ -28,7 +28,7 @@ jobs:
run: |
export PKG_CONFIG_PATH=/usr/lib/64/pkgconfig
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja .
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
cmake --build . --verbose -j4
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
-24
View File
@@ -1,24 +0,0 @@
name: Reusable Spellcheck
on:
workflow_call:
permissions:
contents: read
jobs:
spellcheck:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install codespell
shell: bash
run: |
pip3 install codespell
codespell --version
- name: Run Spellchecker
run: codespell
+34 -5
View File
@@ -45,11 +45,11 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: setup-msys2
uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0
uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0
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 }}-directx-headers
- name: print msys version
run: uname -a
@@ -61,7 +61,7 @@ jobs:
run: cmake --build . --verbose -j4
- name: copy necessary dlls
run: cp /${{ inputs.msystem-lower }}/bin/{lua55,libqjs-0}.dll .
run: cp /${{ inputs.msystem-lower }}/bin/lua55.dll .
- name: list features
run: ./fastfetch --list-features
@@ -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,8 @@ 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
# https://github.com/SignPath/github-action-submit-signing-request/issues/18
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 +139,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 -8
View File
@@ -5,13 +5,7 @@ on:
- pull_request
jobs:
spellcheck:
permissions:
contents: read
uses: ./.github/workflows/build-spellcheck.yml
no-features-test:
needs: spellcheck
name: No-features-test
permissions:
contents: read
@@ -138,7 +132,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 +169,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 +197,7 @@ jobs:
- dragonfly-amd64
- solaris-amd64
- omnios-amd64
# - haiku-amd64
- haiku-amd64
- windows-hosts
permissions:
contents: write
+4
View File
@@ -13,3 +13,7 @@ fastfetch.kdev4
*.user.*
*.swp
*.log
/*.zh.md
/.work*
/doc/*.md
/fastfetch.wiki
+134 -3
View File
@@ -1,8 +1,139 @@
# 2.70.0
Changes:
* LocalIP flags on Windows now describe the interface itself — its operational status, its media connection state and its type, for example `ETHERNET_CSMACD`, `IEEE80211` or `SOFTWARE_LOOPBACK` — instead of the `IP_ADAPTER_*` bits.
* libpulse and libddcutil are no longer used on Linux. Package managers should remove them from their fastfetch package.
* QuickJS format scripting has been removed (deprecated in v2.69.0). Migrate scripts to Lua if necessary. (General)
Features:
* Added Sound detection on Android. (Sound, Android)
* Added battery temperature, remaining time and charger type (AC / USB / wireless) detection on Android. (Battery, Android)
* Performance improvements & internal cleanups
* Added Finnish Language Support
Bugfixes:
* Fixed `--config`/`-c` bash completion offering one candidate per word instead of one candidate per file for paths containing spaces. (Completion, Bash)
* Fixed external monitors detected via DDC/CI being randomly reported as built-in on Linux. (#2615, Brightness, Linux)
* Fixed the Vulkan driver name being concatenated with the name of a previously enumerated device on multi-GPU systems. (#2619, Vulkan)
* Fixed the foot terminal font size not being parsed when written as `Font Name-size`, e.g. `Berkeley Mono-12`. (#2624, TerminalFont, Linux)
* Fixed `/boot` being hidden by default on Haiku, where it is the system volume. (Disk, Haiku)
* Improved Snapdragon X1/X2 model name detection on Linux. (#2625, CPU, Linux)
Logos:
* Added filled Artix logo (#2631)
* Renamed PostmarketOS to nura (#2626)
# 2.69.0
Changes:
* ImageMagick 6 is deprecated and will be removed in a future release. It remains available for older Debian and Ubuntu releases; upgrade to ImageMagick 7 when possible.
* The experimental QuickJS format scripting is deprecated, disabled by default, and planned for removal in the next release. Re-enable it with `cmake -DENABLE_QUICKJS=ON`, or migrate scripts to Lua. (General)
* Replaced `--logo-recache` and `logo.recache` with `--logo-cache` and `logo.cache`. Use `true` to reuse or create valid cache entries, `false` to disable caching, or `regen` to force regeneration. (Logo)
* Modules selected with `--structure` now honor their options from the JSON config.
Features:
* Added `logo.position: "auto"` and `--logo-position auto` to place wide logos above module output when the terminal has less than 32 columns available for text. (Logo)
* The position can also be selected in interactive `--gen-config`.
* For now, the default position is `left` for compatibility with previous releases. This may change to `auto` in future releases.
* Improved Bluetooth detection support:
* Added Bluetooth Low Energy detection on Windows and macOS.
* Report device type (LE or classic) and signal quality, if available.
* Added Bluetooth Core 6.0–6.3 version reporting. (BluetoothRadio)
* Improved image log support:
* Image logos on Windows, macOS, and Android now use native image processing libraries instead of ImageMagick.
* For package managers: ImageMagick dependencies can be removed on macOS, Windows and Android as they are no longer used for image logos.
* Windows Terminal supports sixel logos out of the box
* Changes to a source image now correctly invalidate its cache. (Logo)
* Added GIF and APNG support to `--kitty` image protocol. Set `logo.animationFrame` to `0` to play animations in compatible kitty terminals; positive or negative values select a still frame. Android animation requires Android 12 (API 31); terminals without kitty graphics protocol support report an error and fall back to the built-in logo instead. (Logo)
* Added cmake option `-DENABLE_IMAGE_LOGO=<BOOL>` (default `ON`) to disable image logos and reduce binary size. `raw` logos remain available. (Logo)
* Improved Linux support:
* Added RakuOS rum overlay package counting (`{rum}`) and CRUX package detection (`{crux}`). (Packages)
* Added CPU name and frequency detection on SPARC. (CPU)
* Improved COSMIC version detection and added Umbriel version detection. (DE / WM)
* Improved Android support:
* Expanded ROM detection to HarmonyOS, Flyme, OneUI, LineageOS, and many other ROMs. (DE)
* Improved camera detection; Android 7 (API 24) or later is required. (Camera)
* Battery level and charging state are now available to apps; detailed battery information requires root or ADB. (Battery)
* Added display mode, physical size, rotation, and HDR information. App-based detection requires Android 13 (API 33); root or ADB can also detect displays on older versions. (Display)
* Added Wi-Fi interface, connection, standard, and security information. Android 11 (API 30) or later is required. Termux:API is needed for the Wi-Fi permission, and location permission is needed to reveal SSID and BSSID; without location permission the two names appear as `<redacted>`. (Wifi)
* Added Wallpaper detection, which effectively reports `/data/system/users/0/wallpaper`. (Wallpaper)
* Added Media detection, root/ADB only. (Media / Player)
* Improved macOS support:
* Improved process-name detection in Top. (Top)
* Improved wallpaper detection on macOS Sonoma and later. (#2559, Wallpaper)
* Improved Windows support:
* `winget` now counts packages from configured sources, excludes Microsoft Store apps, and caches results. It remains disabled by default. (Packages)
* Player names now match the names shown by Windows (for example, Chrome is reported as Google Chrome). (Player)
* Added battery-level detection for more controllers on Windows and macOS. (Gamepad)
* Improved macOS controller name detection (macOS, Gamepad)
* macOS controller names now match the system; configs that filter by name may need updating.
* Requires macOS 11 or later.
* Improved the built-in HTTP client used by PublicIP and Weather; it now supports custom ports and chunked responses. (PublicIP / Weather)
* Reduced the default `waitTime` for DiskIO, NetIO, and Top from 500 ms to 250 ms. (DiskIO / NetIO / Top)
* Added `general.preload.lua` for loading shared Lua helpers before modules run. It requires a Lua-enabled build and starts the Lua interpreter whenever configured. (General)
* Lua format scripting is now considered stable.
Bugfixes:
* Fixed Snapdragon X2 series model detection on Linux. (#2611, CPU / Linux)
* Fixed image logo caching, padding, and positioning issues, including an iTerm display bug. (Logo)
* Fixed several Windows issues, including Windows Terminal font detection, redirected `--gen-config` output, and gamepad battery reporting for DualShock 4 and Switch controllers. (#2573, TerminalFont; Gamepad; General)
* Fixed formatting and display issues, including ANSI-aware truncation, date/time formatting, and bright key/title colors. (Format / DateTime / Display)
* Fixed `--dynamic-interval` retries and stale results across multiple rounds. (PublicIP / Weather / Display / Monitor / WM / DE / Media / Player / Shell / Terminal)
* Fixed invalid numeric and color options that could cause hangs or excessive output. (Separator / CPUUsage / NetIO / DiskIO / LoadAvg / Colors)
* Fixed JSON output and config validation across several modules, including GPU, Btrfs, Gamepad, Packages, and Custom. (General)
* Fixed GPU memory and device-type reporting, GNOME Classic and Trinity version detection, and default-route family names. (GPU / DE / LocalIP)
* Fixed module output and filtering issues affecting editor failures, sound status, command selection, and ignored keyboard/gamepad devices. (Editor / Sound / Command / Keyboard / Gamepad)
* Fixed package counting with read-only databases and corrected other package detection and JSON output issues. (Packages)
* Fixed Base64 encoding, big-endian handling, and 64-bit value truncation on 32-bit platforms. (General)
Logos:
* Added ALT Atomic
* Removed Zerene
# 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. (Codec, macOS)
* Fixed Alacritty font size never being parsed from the TOML config. (TerminalFont)
* 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)
@@ -3279,7 +3410,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
+321 -134
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.1
VERSION 2.70.0
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")
@@ -78,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)
@@ -91,18 +88,25 @@ cmake_dependent_option(ENABLE_EET "Enable eet" ON "LINUX OR FreeBSD OR OpenBSD O
cmake_dependent_option(ENABLE_DBUS "Enable dbus-1" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_SQLITE3 "Enable sqlite3" ON "LINUX OR FreeBSD OR APPLE OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_RPM "Enable rpm" ON "LINUX OR GNU" OFF)
cmake_dependent_option(ENABLE_IMAGEMAGICK7 "Enable imagemagick 7" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR APPLE OR ANDROID OR WIN32 OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_IMAGEMAGICK6 "Enable imagemagick 6" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR APPLE OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_CHAFA "Enable chafa" ON "ENABLE_IMAGEMAGICK6 OR ENABLE_IMAGEMAGICK7" OFF)
cmake_dependent_option(ENABLE_EGL "Enable egl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR WIN32 OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_GLX "Enable glx" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_OPENCL "Enable opencl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_FREETYPE "Enable freetype" ON "ANDROID" OFF)
cmake_dependent_option(ENABLE_PULSE "Enable pulse" ON "LINUX OR ANDROID OR GNU" OFF)
cmake_dependent_option(ENABLE_DDCUTIL "Enable ddcutil" ON "LINUX" OFF)
cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR DragonFly OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF)
option(ENABLE_IMAGE_LOGO "Enable image logos (sixel / kitty / iTerm / chafa)" ON)
# The four below are declared outside the guard on purpose. `cmake_dependent_option` is a macro: when
# the condition is false it is never expanded, so nothing rewrites the value already sitting in
# CMakeCache.txt, and `cmake -DENABLE_IMAGE_LOGO=OFF .` in an existing build directory would leave
# ENABLE_SIXEL=ON behind -- sixel and chafa would still be compiled and linked while image.c, the
# only file that uses them, is not. The condition does the disabling instead: it is the fourth
# argument's job, and it keeps the option visible and settable in cmake-gui either way.
cmake_dependent_option(ENABLE_IMAGEMAGICK7 "Enable imagemagick 7" ON "ENABLE_IMAGE_LOGO;LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_IMAGEMAGICK6 "Enable imagemagick 6" ON "ENABLE_IMAGE_LOGO;LINUX OR FreeBSD OR OpenBSD OR NetBSD OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_SIXEL "Enable sixel logo output via the embedded libsixel encoder (Windows, macOS and Android; replaces ImageMagick's SIXEL coder)" ON "ENABLE_IMAGE_LOGO;WIN32 OR APPLE OR ANDROID" OFF)
cmake_dependent_option(ENABLE_CHAFA "Enable chafa" ON "ENABLE_IMAGE_LOGO;ENABLE_IMAGEMAGICK6 OR ENABLE_IMAGEMAGICK7 OR WIN32 OR APPLE OR ANDROID" OFF)
option(ENABLE_ZLIB "Enable zlib" ON)
option(ENABLE_SYSTEM_YYJSON "Use system provided (instead of fastfetch embedded) yyjson library" OFF)
option(ENABLE_ASAN "Build fastfetch with ASAN (address sanitizer)" OFF)
@@ -117,7 +121,6 @@ option(ENABLE_EMBEDDED_PCIIDS "Embed pci.ids into fastfetch, requires `python`"
option(ENABLE_EMBEDDED_AMDGPUIDS "Embed amdgpu.ids into fastfetch, requires `python`" OFF)
option(ENABLE_WORDEXP "Enable using of wordexp(3) if available, instead of glob(3)" ON)
option(ENABLE_LUA "Enable lua scripting for format strings" ON)
option(ENABLE_QUICKJS "Enable quickjs scripting for format strings" ON)
option(ENABLE_LIBZFS "Enable libzfs" ON)
option(ENABLE_WCWIDTH "Detect wide-width characters with wcwidth when calculating logo and separator width. Disable for a smaller and slightly faster build, but logos containing CJK/emoji may be measured too narrow and render incorrectly. Such usecases are uncommon." ON)
set(DEFAULT_STRUCTURE "Title:Separator:OS:Host:Kernel:Uptime:Packages:Shell:Display:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:GPU:Memory:Swap:Disk:LocalIp:Battery:PowerAdapter:Locale:Break:Colors" CACHE STRING "Default module structure for detection")
@@ -128,12 +131,38 @@ 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 "")
set(FF_MODULE_SRC_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()
list(APPEND FF_MODULE_SRC_DIRS "${FF_MODULE_DIR}")
endforeach()
unset(FF_MODULE_SRCS)
# A directory of sources whose name no source carries registers no module at all, and nothing else
# in the build says so -- the module simply never appears. Reported rather than left to be noticed.
# Directories are compared rather than sources, so that a module which keeps helper sources next to
# its own `<module>.c` does not warn about every one of them.
list(REMOVE_DUPLICATES FF_MODULE_SRC_DIRS)
foreach(FF_MODULE_DIR ${FF_MODULE_SRC_DIRS})
if(NOT FF_MODULE_DIR IN_LIST FF_MODULE_DIRS)
message(WARNING "src/modules/${FF_MODULE_DIR}/ contains C sources but no ${FF_MODULE_DIR}.c, "
"so no module is registered for it. A module directory must hold a source file named after it.")
endif()
endforeach()
unset(FF_MODULE_SRC_DIRS)
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)
@@ -251,6 +280,22 @@ if(ENABLE_LTO AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
endif()
endif()
# Release and MinSizeRel drop the symbol table and the unwind metadata; Debug and RelWithDebInfo keep
# both, so those stay fully debuggable. CMake does not strip MinSizeRel on its own.
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
if(NOT APPLE) # ld: warning: -s is obsolete
# The symbol table is dead weight: 437 KB (16%) of a MinGW build, 91 KB (3%) of a GCC build.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-s")
endif()
# Drops .eh_frame / .eh_frame_hdr on ELF (74 KB) and .pdata / .xdata on PE (26 KB). Every C++
# translation unit here is built with -fno-exceptions -fno-rtti and no source file contains a
# single try / catch, so no exception can ever unwind through these frames. The only loss is
# backtrace() / gdb / perf / crash-dump stack walking in a release binary.
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-asynchronous-unwind-tables")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-asynchronous-unwind-tables")
endif()
#######################
# Target FS structure #
#######################
@@ -431,6 +476,7 @@ file(GENERATE OUTPUT logo_builtin.h CONTENT "${LOGO_BUILTIN_H}")
set(LIBFASTFETCH_SRC
src/common/impl/commandoption.c
src/common/impl/debug.c
src/common/impl/duration.c
src/common/impl/font.c
src/common/impl/format.c
@@ -453,6 +499,7 @@ set(LIBFASTFETCH_SRC
src/common/impl/time.c
src/common/impl/edidHelper.c
src/common/impl/base64.c
src/common/impl/FFcache.c
src/common/impl/FFlist.c
src/common/impl/FFstrbuf.c
src/common/impl/path.c
@@ -484,6 +531,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
@@ -491,91 +539,33 @@ set(LIBFASTFETCH_SRC
src/detection/weather/weather.c
src/detection/zpool/zpool.c
src/logo/builtin.c
src/logo/image/im6.c
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
)
if(ENABLE_IMAGE_LOGO)
list(APPEND LIBFASTFETCH_SRC
src/logo/image/image.c
src/logo/image/im6.c
src/logo/image/im7.c
src/logo/image/sixel.c
)
if(ANDROID)
list(APPEND LIBFASTFETCH_SRC
src/logo/image/aid.c
)
endif()
endif()
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
@@ -651,6 +641,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
@@ -663,6 +654,9 @@ if(LINUX)
)
elseif(ANDROID)
list(APPEND LIBFASTFETCH_SRC
src/common/android/binder.c
src/common/android/dex.c
src/common/android/package.c
src/common/impl/dbus.c
src/common/impl/io_unix.c
src/common/impl/netif_linux.c
@@ -691,6 +685,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
@@ -709,6 +710,7 @@ elseif(ANDROID)
src/detection/locale/locale_linux.c
src/detection/localip/localip_linux.c
src/detection/gamepad/gamepad_nosupport.c
src/detection/media/media_android.c
src/detection/media/media_linux.c
src/detection/memory/memory_linux.c
src/detection/mouse/mouse_nosupport.c
@@ -719,6 +721,7 @@ elseif(ANDROID)
src/detection/packages/packages_nix.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_linux.c
src/detection/sound/sound_android.c
src/detection/sound/sound_linux.c
src/detection/swap/swap_linux.c
src/detection/terminalfont/terminalfont_android.c
@@ -726,9 +729,11 @@ 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
src/detection/wallpaper/wallpaper_android.c
src/detection/wallpaper/wallpaper_linux.c
src/detection/wifi/wifi_android.c
src/detection/wm/wm_linux.c
@@ -802,7 +807,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
@@ -822,11 +826,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()
@@ -904,6 +912,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
@@ -964,7 +973,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
@@ -990,6 +999,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
@@ -1035,12 +1045,12 @@ 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
src/detection/localip/localip_linux.c
src/detection/gamepad/gamepad_apple.c
src/detection/gamepad/gamepad_apple.m
src/detection/media/media_apple.m
src/detection/memory/memory_apple.c
src/detection/mouse/mouse_apple.c
@@ -1050,13 +1060,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
@@ -1067,6 +1078,11 @@ elseif(APPLE)
src/detection/wmtheme/wmtheme_apple.c
src/detection/camera/camera_apple.m
)
if(ENABLE_IMAGE_LOGO)
list(APPEND LIBFASTFETCH_SRC
src/logo/image/imageio.c
)
endif()
# CMAKE_SYSTEM_PROCESSOR has been normalized before
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64")
list(APPEND LIBFASTFETCH_SRC
@@ -1093,17 +1109,19 @@ elseif(WIN32)
src/common/impl/processing_windows.c
src/common/impl/FFPlatform_windows.c
src/common/impl/binary_windows.c
src/common/impl/debug_windows.c
src/common/impl/kmod_windows.c
src/common/windows/getline.c
src/common/windows/com.c
src/common/windows/folders.c
src/common/windows/registry.c
src/common/windows/unicode.c
src/common/windows/variant.cpp
src/common/windows/winrt.cpp
src/common/windows/version.c
src/detection/battery/battery_windows.c
src/detection/bios/bios_windows.c
src/detection/bluetooth/bluetooth_windows.c
src/detection/bluetooth/bluetooth_windows.cpp
src/detection/bluetoothradio/bluetoothradio_windows.c
src/detection/board/board_windows.c
src/detection/bootmgr/bootmgr_windows.c
@@ -1128,7 +1146,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
@@ -1149,6 +1167,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
@@ -1159,6 +1178,11 @@ elseif(WIN32)
src/detection/wmtheme/wmtheme_windows.c
src/detection/camera/camera_windows.cpp
)
if(ENABLE_IMAGE_LOGO)
list(APPEND LIBFASTFETCH_SRC
src/logo/image/wic.cpp
)
endif()
elseif(SunOS)
list(APPEND LIBFASTFETCH_SRC
src/common/impl/dbus.c
@@ -1169,7 +1193,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
@@ -1210,7 +1234,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
@@ -1223,7 +1247,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
@@ -1231,11 +1255,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
@@ -1301,11 +1326,12 @@ 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
@@ -1364,7 +1390,7 @@ elseif(GNU)
src/detection/keyboard/keyboard_nosupport.c
src/detection/libc/libc_linux.c
src/detection/lm/lm_linux.c
src/detection/loadavg/loadavg_linux.c
src/detection/loadavg/loadavg_sunos.c
src/detection/locale/locale_linux.c
src/detection/localip/localip_linux.c
src/detection/gamepad/gamepad_nosupport.c
@@ -1377,7 +1403,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
@@ -1385,6 +1411,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
@@ -1459,16 +1486,29 @@ else()
)
endif()
if(ENABLE_SIXEL)
set(FF_SIXEL_SRC
src/3rdparty/sixel/allocator.c
src/3rdparty/sixel/dither.c
src/3rdparty/sixel/output.c
src/3rdparty/sixel/pixelformat.c
src/3rdparty/sixel/quant.c
src/3rdparty/sixel/status.c
src/3rdparty/sixel/tosixel.c
)
list(APPEND LIBFASTFETCH_SRC ${FF_SIXEL_SRC})
set_source_files_properties(${FF_SIXEL_SRC} PROPERTIES COMPILE_OPTIONS "-Wno-conversion")
endif()
add_library(libfastfetch OBJECT
${LIBFASTFETCH_SRC}
)
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()
@@ -1501,10 +1541,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)
@@ -1819,23 +1860,10 @@ ff_lib_enable(FREETYPE
"freetype2"
"FreeType2"
)
ff_lib_enable(PULSE
"libpulse"
"Pulse"
)
ff_lib_enable(DDCUTIL
"ddcutil"
"Ddcutil"
)
ff_lib_enable(ELF
"libelf"
"libelf"
)
ff_lib_enable(QUICKJS
"qjs"
"qjs"
"qjs"
)
if(ENABLE_LUA)
# Lua is a special case since its CMake config does not follow the usual pattern
find_package(Lua QUIET)
@@ -1869,6 +1897,10 @@ if(ENABLE_THREADS)
endif()
endif()
if(ENABLE_IMAGE_LOGO)
target_compile_definitions(libfastfetch PUBLIC FF_HAVE_IMAGE_LOGO=1)
endif()
if(ENABLE_EMBEDDED_PCIIDS)
target_compile_definitions(libfastfetch PUBLIC FF_HAVE_EMBEDDED_PCIIDS=1)
endif()
@@ -1906,24 +1938,28 @@ if(LINUX)
elseif(APPLE)
target_link_libraries(libfastfetch
PRIVATE "-framework AVFoundation"
PRIVATE "-framework Accelerate"
PRIVATE "-framework Cocoa"
PRIVATE "-framework CoreFoundation"
PRIVATE "-framework CoreAudio"
PRIVATE "-framework CoreBluetooth"
PRIVATE "-framework CoreFoundation"
PRIVATE "-framework CoreMedia"
PRIVATE "-framework CoreVideo"
PRIVATE "-framework CoreWLAN"
PRIVATE "-framework GameController"
PRIVATE "-framework IOBluetooth"
PRIVATE "-framework IOKit"
PRIVATE "-framework ImageIO"
PRIVATE "-framework Metal"
PRIVATE "-framework OpenGL"
PRIVATE "-framework OpenCL"
PRIVATE "-framework OpenGL"
PRIVATE "-framework SystemConfiguration"
PRIVATE "-framework VideoToolbox"
PRIVATE "-weak_framework CoreDisplay"
PRIVATE "-F /System/Library/PrivateFrameworks"
PRIVATE "-weak_framework DisplayServices"
PRIVATE "-weak_framework MediaRemote"
PRIVATE "-weak_framework SkyLight"
)
elseif(WIN32)
target_link_libraries(libfastfetch
@@ -1939,7 +1975,17 @@ elseif(WIN32)
PRIVATE "cfgmgr32"
PRIVATE "winbrand"
PRIVATE "secur32"
PRIVATE "shlwapi"
)
if(ENABLE_IMAGE_LOGO)
# Only src/logo/image/wic.cpp uses WIC, and that file is only compiled with image logos
# enabled (see the WIN32 source list above). Verified by building with
# `-DENABLE_IMAGE_LOGO=OFF` and removing the library from the link line: it links and runs
# without it, and nothing else in the tree references IWIC*.
target_link_libraries(libfastfetch
PRIVATE "windowscodecs"
)
endif()
if(NOT ENABLE_WIN81_COMPAT)
target_link_libraries(libfastfetch
PRIVATE "mincore"
@@ -1973,7 +2019,6 @@ elseif(FreeBSD)
elseif(OpenBSD)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE "kvm"
PRIVATE "sndio"
PRIVATE "util"
)
@@ -1995,10 +2040,14 @@ elseif(SunOS)
elseif(GNU)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE "ps"
)
elseif(ANDROID)
target_link_libraries(libfastfetch
PRIVATE "m"
PRIVATE jnigraphics
PRIVATE mediandk
PRIVATE camera2ndk
)
if(ENABLE_WORDEXP)
# https://github.com/termux/termux-packages/pull/7056
@@ -2016,6 +2065,38 @@ elseif(ANDROID)
endif()
endif()
endif()
# Three APIs are newer than the API level this build targets: AImageDecoder is 30,
# AndroidBitmap_compress is 30, and AMediaCodec_getName / AMediaCodec_releaseName are 28.
# fastfetch keeps running on devices below those levels, and the NDK headers mark the entry
# points unavailable there, so they have to be weak references -- a strong reference would fail
# the load on an older device. Guarding every use with __builtin_available() is what makes that
# safe, and turning the guard diagnostic into an error keeps a use from slipping past it.
#
# Each one lives in the library that provides it, and that library is linked above:
# AImageDecoder_* and AndroidBitmap_compress are exported by libjnigraphics.so, and
# AMediaCodec_* by libmediandk.so. Checked on a device (Android 16, API 36) with
# `readelf --dyn-syms /system/lib64/libjnigraphics.so` and a dlopen/dlsym probe: every
# AImageDecoder_* entry point resolves there and none of them resolves in libandroid.so, which
# is why libandroid.so is *not* linked -- nothing fastfetch calls comes from it. Keep the
# provider library linked for the reason the NDK requires it: an undefined weak symbol resolves
# to NULL rather than failing the load, and FF_ANDROID_API_AT_LEAST() reads the platform level,
# not the symbol, so it would send the call straight through a NULL pointer on every device new
# enough to pass the guard.
#
# camera_android.c is deliberately not in this list. The camera2 NDK is a *library* that starts at
# API 24, and a weak reference weakens the symbol, never the library: DT_NEEDED would still name
# libcamera2ndk.so, and a device below API 24 would fail to load fastfetch at all. The NDK's guide
# is explicit that there is no such thing as a weak library reference, and that dlopen is the only
# way to reach a library newer than minSdkVersion. Nothing here pins ANDROID_PLATFORM, so a build
# aimed below 24 would need that; on the supported path -- Termux, which targets 24 -- the library
# is always present and the plain call is correct. libjnigraphics itself is one of them and is
# linked unconditionally above, so the encoder only needs its symbol weakened, not its library.
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/src/logo/image/aid.c
${CMAKE_CURRENT_SOURCE_DIR}/src/detection/media/media_android.c
${CMAKE_CURRENT_SOURCE_DIR}/src/detection/codec/codec_android.c
PROPERTIES COMPILE_OPTIONS "-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__;-Werror=unguarded-availability")
elseif(Haiku)
target_link_libraries(libfastfetch
PRIVATE "network"
@@ -2035,6 +2116,16 @@ target_include_directories(libfastfetch
PUBLIC ${PROJECT_SOURCE_DIR}/src
)
if(ENABLE_SIXEL)
# src/3rdparty/sixel/*.c include <sixel.h> (angle brackets, so the directory itself
# has to be on the include path)
target_include_directories(libfastfetch PRIVATE ${PROJECT_SOURCE_DIR}/src/3rdparty/sixel)
# The vendored subset is not a library to be detected, so FF_HAVE_SIXEL can't come
# from ff_lib_enable() and has to be defined here
target_compile_definitions(libfastfetch PUBLIC FF_HAVE_SIXEL=1)
endif()
target_link_libraries(libfastfetch
PRIVATE ${CMAKE_DL_LIBS}
)
@@ -2172,6 +2263,20 @@ endif()
###################
if (BUILD_TESTS)
add_executable(fastfetch-test-cache
tests/cache.c
)
target_link_libraries(fastfetch-test-cache
PRIVATE libfastfetch
)
add_executable(fastfetch-test-logo
tests/logo.c
)
target_link_libraries(fastfetch-test-logo
PRIVATE libfastfetch
)
add_executable(fastfetch-test-strbuf
tests/strbuf.c
)
@@ -2214,13 +2319,95 @@ if (BUILD_TESTS)
PRIVATE libfastfetch
)
add_executable(fastfetch-test-networking
tests/networking.c
)
target_link_libraries(fastfetch-test-networking
PRIVATE libfastfetch
)
add_executable(fastfetch-test-base64
tests/base64.c
)
target_link_libraries(fastfetch-test-base64
PRIVATE libfastfetch
)
add_executable(fastfetch-test-parsing
tests/parsing.c
)
target_link_libraries(fastfetch-test-parsing
PRIVATE libfastfetch
)
add_executable(fastfetch-test-properties
tests/properties.c
)
target_link_libraries(fastfetch-test-properties
PRIVATE libfastfetch
)
add_executable(fastfetch-test-size
tests/size.c
)
target_link_libraries(fastfetch-test-size
PRIVATE libfastfetch
)
add_executable(fastfetch-test-frequency
tests/frequency.c
)
target_link_libraries(fastfetch-test-frequency
PRIVATE libfastfetch
)
add_executable(fastfetch-test-percent
tests/percent.c
)
target_link_libraries(fastfetch-test-percent
PRIVATE libfastfetch
)
add_executable(fastfetch-test-temps
tests/temps.c
)
target_link_libraries(fastfetch-test-temps
PRIVATE libfastfetch
)
add_executable(fastfetch-test-endian
tests/endian.c
)
target_link_libraries(fastfetch-test-endian
PRIVATE libfastfetch
)
add_executable(fastfetch-test-endian-bigendian
tests/endian-bigendian.c
)
target_link_libraries(fastfetch-test-endian-bigendian
PRIVATE libfastfetch
)
enable_testing()
add_test(NAME test-cache COMMAND fastfetch-test-cache)
add_test(NAME test-logo COMMAND fastfetch-test-logo)
add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf)
add_test(NAME test-list COMMAND fastfetch-test-list)
add_test(NAME test-format COMMAND fastfetch-test-format)
add_test(NAME test-color COMMAND fastfetch-test-color)
add_test(NAME test-duration COMMAND fastfetch-test-duration)
add_test(NAME test-strutil COMMAND fastfetch-test-strutil)
add_test(NAME test-networking COMMAND fastfetch-test-networking)
add_test(NAME test-base64 COMMAND fastfetch-test-base64)
add_test(NAME test-parsing COMMAND fastfetch-test-parsing)
add_test(NAME test-properties COMMAND fastfetch-test-properties)
add_test(NAME test-size COMMAND fastfetch-test-size)
add_test(NAME test-frequency COMMAND fastfetch-test-frequency)
add_test(NAME test-percent COMMAND fastfetch-test-percent)
add_test(NAME test-temps COMMAND fastfetch-test-temps)
add_test(NAME test-endian COMMAND fastfetch-test-endian)
add_test(NAME test-endian-bigendian COMMAND fastfetch-test-endian-bigendian)
endif()
##################
+792
View File
@@ -0,0 +1,792 @@
# 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, 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 1–9; at most 9 are supported (`FASTFETCH_LOGO_MAX_COLORS = 9`)
- `$$` is a literal `$`
- Characters without a placeholder inherit the current color. Of the 530 existing logos, 239 use no placeholders at all (monochrome) and 291 do; **new logos should use placeholders**, 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` |
### 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
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`)
+21 -38
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)
@@ -8,8 +9,8 @@
[![homebrew downloads](https://img.shields.io/homebrew/installs/dm/fastfetch?logo=homebrew)](https://formulae.brew.sh/formula/fastfetch#default)
[![GitHub all releases](https://img.shields.io/github/downloads/fastfetch-cli/fastfetch/total?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases)
[![GitHub release (with filter)](https://img.shields.io/github/v/release/fastfetch-cli/fastfetch?logo=github)](https://github.com/fastfetch-cli/fastfetch/releases)
[![latest packaged version(s)](https://repology.org/badge/latest-versions/fastfetch.svg)](https://repology.org/project/fastfetch/versions)
[![Packaging status](https://repology.org/badge/tiny-repos/fastfetch.svg)](https://repology.org/project/fastfetch/versions)
[![latest packaged version(s)](https://repology.amdmi3.ru/badge/latest-versions/fastfetch.svg)](https://repology.amdmi3.ru/project/fastfetch/versions)
[![Packaging status](https://repology.amdmi3.ru/badge/tiny-repos/fastfetch.svg)](https://repology.amdmi3.ru/project/fastfetch/versions)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/fastfetch-cli/fastfetch)
[![中文README](https://img.shields.io/badge/%E4%B8%AD%E6%96%87-README-red)](README-cn.md)
@@ -35,8 +36,8 @@ There are [screenshots on different platforms](https://github.com/fastfetch-cli/
Some distributions package outdated versions of fastfetch. Older versions receive no support, so please always try to use the latest version.
<a href="https://repology.org/project/fastfetch/versions">
<img src="https://repology.org/badge/vertical-allrepos/fastfetch.svg?columns=2" alt="Packaging status" align="right">
<a href="https://repology.amdmi3.ru/project/fastfetch/versions">
<img src="https://repology.amdmi3.ru/badge/vertical-allrepos/fastfetch.svg?columns=2" alt="Packaging status" align="right">
</a>
* Ubuntu: [`ppa:zhangsongcui3371/fastfetch`](https://launchpad.net/~zhangsongcui3371/+archive/ubuntu/fastfetch) (Ubuntu 22.04 or newer; latest version)
@@ -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
@@ -247,7 +248,20 @@ Set the key to a white space.
### Q: How can I display images on Windows?
As of April 2025:
#### Windows Terminal
Windows Terminal supports the sixel image protocol only.
```jsonc
{
"logo": {
"type": "sixel", // DO NOT USE "auto"
"source": "C:/path/to/image.png", // ~ can be used to refer to the Windows user home directory
"width": <image-width-in-chars>, // Optional
"height": <image-height-in-chars> // Optional
}
}
```
#### mintty and Wezterm
@@ -259,38 +273,7 @@ In `config.jsonc`:
"logo": {
"type": "iterm",
"source": "C:/path/to/image.png",
"width": <num-in-chars>
}
}
```
#### Windows Terminal
Windows Terminal supports the sixel image protocol only.
* If you installed fastfetch through MSYS2:
1. Install imagemagick: `pacman -S mingw-w64-<subsystem>-x86_64-imagemagick`
2. In `config.jsonc`:
```jsonc
{
"logo": {
"type": "sixel", // DO NOT USE "auto"
"source": "C:/path/to/image.png", // Do NOT use `~` as fastfetch is a native Windows program and doesn't apply cygwin path conversion
"width": <image-width-in-chars>, // Optional
"height": <image-height-in-chars> // Optional
}
}
```
* If you installed fastfetch via scoop or downloaded the binary directly from the GitHub Releases page:
1. Convert your image manually to sixel format using [any online image conversion service](https://www.google.com/search?q=convert+image+to+sixel)
2. In `config.jsonc`:
```jsonc
{
"logo": {
"type": "raw", // DO NOT USE "auto"
"source": "C:/path/to/image.sixel",
"width": <image-width-in-chars>, // Required
"height": <image-height-in-chars> // Required
"width": <image-width-in-chars>
}
}
```
+3 -1
View File
@@ -33,7 +33,9 @@ _fastfetch() {
if type _filedir &>/dev/null; then
_filedir
elif type compgen &>/dev/null; then
COMPREPLY+=($(compgen -f -- "$cur"))
local -a files
readarray -t files < <(compgen -f -- "$cur")
COMPREPLY+=("${files[@]}")
fi
return
;;
+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__":
+12
View File
@@ -1,3 +1,15 @@
fastfetch (2.68.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.68.1
-- Carter Li <zhangsongcui@live.cn> Wed, 02 Sep 2026 10:47:51 +0800
fastfetch (2.68.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.68.0
-- Carter Li <zhangsongcui@live.cn> Tue, 01 Sep 2026 16:34:36 +0800
fastfetch (2.67.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium
* Update to 2.67.0
+1 -1
View File
@@ -2,7 +2,7 @@ Source: fastfetch
Section: universe/utils
Priority: optional
Maintainer: Carter Li <zhangsongcui@live.cn>
Build-Depends: libelf-dev, 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, libddcutil-dev, libchafa-dev, libefl-all-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools
Build-Depends: libelf-dev, 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, libdrm-dev, libchafa-dev, libefl-all-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools
Standards-Version: 4.0.0
Homepage: https://github.com/fastfetch-cli/fastfetch
+68 -4
View File
@@ -195,6 +195,7 @@
"file-raw": "Text file, printed as is",
"data": "Text data, printed with color code replacement",
"data-raw": "Text data, printed as is",
"command-raw": "Command to run, whose output is printed as is",
"sixel": "Image file, rendered as sixel",
"kitty": "Image file, rendered using the Kitty graphics protocol",
"kitty-direct": "Image file, tells the terminal emulator to read image data from the specified file",
@@ -269,6 +270,14 @@
"type": "num"
}
},
{
"long": "logo-padding-bottom",
"desc": "Set the padding at the bottom of the logo",
"remark": "Only used with \"--logo-position top\"",
"arg": {
"type": "num"
}
},
{
"long": "logo-print-remaining",
"desc": "Specify whether to print remaining logo lines when the logo is taller than the module list",
@@ -284,6 +293,7 @@
"arg": {
"type": "enum",
"enum": {
"auto": "Automatically choose left or top based on terminal width",
"left": "Left",
"top": "Top",
"right": "Right"
@@ -291,12 +301,33 @@
}
},
{
"long": "logo-recache",
"desc": "If true, regenerate the image logo cache",
"long": "logo-cache",
"desc": "Specify how the image logo cache is used",
"remark": "See \"--help logo-animation-frame\" for the one case where a cached rendering can outlive the image type that produced it",
"arg": {
"type": "bool",
"type": "enum",
"optional": true,
"default": false
"default": "true",
"enum": {
"true": "Reuse a cached rendering when it is valid, and write it back on a cache miss",
"false": "Ignore the image logo cache completely: neither read nor write it",
"regen": "Ignore any existing cached rendering and regenerate it"
}
}
},
{
"long": "logo-animation-frame",
"desc": "Select which frame of an animated image logo to render",
"remark": [
"Animated kitty graphics only, and only if the image source is animated; 1 or higher renders that frame as a still image, 0 plays the animation",
"A selected frame is cached exactly like the first frame is, sharing its entries; the frame number is recorded next to them, so changing this option re-renders",
"That record is per cache directory, not per image type: switching --logo-type between two types that both have a cached rendering here, and asking for a different frame, can pair one type's rendering with the other's frame number",
"Run \"--logo-cache regen\" after such a switch"
],
"arg": {
"type": "num",
"optional": true,
"default": 1
}
},
{
@@ -585,6 +616,39 @@
"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",
"fi": "Finnish",
"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",
+339 -65
View File
@@ -19,15 +19,14 @@
},
"key": {
"description": "Module key\nUse a single space (` `) to hide the key",
"type": "string",
"minLength": 1
"type": "string"
},
"keyColor": {
"description": "Color of the module key. Overrides `display.color.key`",
"description": "Color of the module key. Overrides `display.color.keys`",
"$ref": "#/$defs/colors"
},
"keyWidth": {
"description": "Width of the module key. Overrides `display.keyWidth`",
"description": "Width of the module key. Overrides `display.key.width`",
"type": "integer",
"minimum": 1
},
@@ -131,7 +130,9 @@
"SunOS",
"OpenBSD",
"NetBSD",
"Haiku"
"Haiku",
"GNU",
"Unknown"
]
},
"architectures": {
@@ -150,7 +151,9 @@
"sparc",
"alpha",
"hppa",
"m68k"
"sh",
"m68k",
"Unknown"
]
},
"conditions": {
@@ -274,19 +277,19 @@
"batteryFormat": {
"description": "Output format for the `Battery` module. See Wiki for formatting syntax\n 1. {manufacturer}: Battery manufacturer\n 2. {model-name}: Battery model name\n 3. {technology}: Battery technology\n 4. {capacity}: Battery capacity (percentage num)\n 5. {status}: Battery status\n 6. {temperature}: Battery temperature (formatted)\n 7. {cycle-count}: Battery cycle count\n 8. {serial}: Battery serial number\n 9. {manufacture-date}: Battery manufacture date\n 10. {capacity-bar}: Battery capacity (percentage bar)\n 11. {time-days}: Battery time remaining days\n 12. {time-hours}: Battery time remaining hours\n 13. {time-minutes}: Battery time remaining minutes\n 14. {time-seconds}: Battery time remaining seconds\n 15. {time-formatted}: Battery time remaining (formatted)",
"description": "Output format for the `Battery` module. See Wiki for formatting syntax\n 1. {manufacturer}: Battery manufacturer\n 2. {name}: Battery model name *\n 3. {technology}: Battery technology\n 4. {capacity}: Battery capacity (percentage num)\n 5. {status}: Battery status\n 6. {temperature}: Battery temperature (formatted)\n 7. {cycle-count}: Battery cycle count\n 8. {serial}: Battery serial number\n 9. {manufacture-date}: Battery manufacture date\n 10. {capacity-bar}: Battery capacity (percentage bar)\n 11. {time-days}: Battery time remaining days\n 12. {time-hours}: Battery time remaining hours\n 13. {time-minutes}: Battery time remaining minutes\n 14. {time-seconds}: Battery time remaining seconds\n 15. {time-formatted}: Battery time remaining (formatted)",
"type": "string"
},
"biosFormat": {
"description": "Output format for the `BIOS` module. See Wiki for formatting syntax\n 1. {date}: BIOS date\n 2. {release}: BIOS release\n 3. {vendor}: BIOS vendor\n 4. {version}: BIOS version\n 5. {type}: Firmware type",
"description": "Output format for the `BIOS` module. See Wiki for formatting syntax\n 1. {date}: BIOS date\n 2. {release}: BIOS release\n 3. {vendor}: BIOS vendor\n 4. {version}: BIOS version\n 5. {type}: Firmware type *",
"type": "string"
},
"bluetoothFormat": {
"description": "Output format for the `Bluetooth` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {address}: Address\n 3. {type}: Type\n 4. {battery-percentage}: Battery percentage number\n 5. {connected}: Is connected\n 6. {battery-percentage-bar}: Battery percentage bar",
"description": "Output format for the `Bluetooth` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {address}: Address\n 3. {type}: Type\n 4. {device-type}: Bluetooth stacks the device answers on\n 5. {battery-percentage}: Battery percentage number\n 6. {battery-percentage-bar}: Battery percentage bar\n 7. {signal-quality}: Signal quality number\n 8. {signal-quality-bar}: Signal quality bar\n 9. {connected}: Is connected",
"type": "string"
},
"bluetoothradioFormat": {
"description": "Output format for the `BluetoothRadio` module. See Wiki for formatting syntax\n 1. {name}: Radio name for discovering\n 2. {address}: Address\n 3. {lmp-version}: LMP version\n 4. {lmp-subversion}: LMP subversion\n 5. {version}: Bluetooth version\n 6. {vendor}: Vendor\n 7. {discoverable}: Discoverable\n 8. {connectable}: Connectable / Pairable",
"description": "Output format for the `BluetoothRadio` module. See Wiki for formatting syntax\n 1. {name}: Radio name for discovering *\n 2. {address}: Address\n 3. {lmp-version}: LMP version\n 4. {lmp-subversion}: LMP subversion\n 5. {version}: Bluetooth version\n 6. {vendor}: Vendor\n 7. {discoverable}: Discoverable\n 8. {connectable}: Connectable / Pairable",
"type": "string"
},
"boardFormat": {
@@ -298,11 +301,11 @@
"type": "string"
},
"brightnessFormat": {
"description": "Output format for the `Brightness` module. See Wiki for formatting syntax\n 1. {percentage}: Screen brightness (percentage num)\n 2. {name}: Screen name\n 3. {max}: Maximum brightness value\n 4. {min}: Minimum brightness value\n 5. {current}: Current brightness value\n 6. {percentage-bar}: Screen brightness (percentage bar)\n 7. {is-builtin}: Is built-in screen",
"description": "Output format for the `Brightness` module. See Wiki for formatting syntax\n 1. {percentage}: Screen brightness (percentage num)\n 2. {name}: Screen name *\n 3. {max}: Maximum brightness value\n 4. {min}: Minimum brightness value\n 5. {current}: Current brightness value\n 6. {percentage-bar}: Screen brightness (percentage bar)\n 7. {is-builtin}: Is built-in screen",
"type": "string"
},
"btrfsFormat": {
"description": "Output format for the `Btrfs` module. See Wiki for formatting syntax\n 1. {name}: Name / Label\n 2. {uuid}: UUID\n 3. {devices}: Associated devices\n 4. {features}: Enabled features\n 5. {used}: Size used\n 6. {allocated}: Size allocated\n 7. {total}: Size total\n 8. {used-percentage}: Used percentage num\n 9. {allocated-percentage}: Allocated percentage num\n 10. {used-percentage-bar}: Used percentage bar\n 11. {allocated-percentage-bar}: Allocated percentage bar\n 12. {node-size}: Node size\n 13. {sector-size}: Sector size",
"description": "Output format for the `Btrfs` module. See Wiki for formatting syntax\n 1. {name}: Name / Label *\n 2. {uuid}: UUID\n 3. {devices}: Associated devices\n 4. {features}: Enabled features\n 5. {used}: Size used\n 6. {allocated}: Size allocated\n 7. {total}: Size total\n 8. {used-percentage}: Used percentage num\n 9. {allocated-percentage}: Allocated percentage num\n 10. {used-percentage-bar}: Used percentage bar\n 11. {allocated-percentage-bar}: Allocated percentage bar\n 12. {node-size}: Node size\n 13. {sector-size}: Sector size",
"type": "string"
},
"cameraFormat": {
@@ -314,7 +317,7 @@
"type": "string"
},
"codecFormat": {
"description": "Output format for the `Codec` module. See Wiki for formatting syntax\n 1. {gpu}: GPU name\n 2. {direction}: Decoder / Encoder\n 3. {types}: Compatibility alias of codec types\n 4. {platform-api}: Platform API used for detection",
"description": "Output format for the `Codec` module. See Wiki for formatting syntax\n 1. {gpu}: GPU name *\n 2. {direction}: Decoder / Encoder *\n 3. {types}: Compatibility alias of codec types\n 4. {platform-api}: Platform API used for detection",
"type": "string"
},
"commandFormat": {
@@ -322,11 +325,11 @@
"type": "string"
},
"cpuFormat": {
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count\n 13. {code-name}: CPU code name\n 14. {technology}: CPU technology",
"description": "Output format for the `CPU` module. See Wiki for formatting syntax\n 1. {name}: Name\n 2. {vendor}: Vendor\n 3. {cores-physical}: Physical core count\n 4. {cores-logical}: Logical core count\n 5. {cores-online}: Online core count\n 6. {freq-base}: Base frequency (formatted)\n 7. {freq-max}: Max frequency (formatted)\n 8. {temperature}: Temperature (formatted)\n 9. {core-types}: Logical core count grouped by frequency\n 10. {packages}: Processor package count\n 11. {march}: CPU microarchitecture\n 12. {numa-nodes}: NUMA node count\n 13. {code-name}: CPU code name (x86 only)\n 14. {technology}: CPU technology (x86 only)",
"type": "string"
},
"cpucacheFormat": {
"description": "Output format for the `CPUCache` module. See Wiki for formatting syntax\n 1. {result}: Separate result\n 2. {sum}: Sum result",
"description": "Output format for the `CPUCache` module. See Wiki for formatting syntax\n 1. {result}: Separate result\n 2. {sum}: Sum result\n 3. {level}: Cache level *",
"type": "string"
},
"cpuusageFormat": {
@@ -346,15 +349,15 @@
"type": "string"
},
"displayFormat": {
"description": "Output format for the `Display` module. See Wiki for formatting syntax\n 1. {width}: Screen configured width (in pixels)\n 2. {height}: Screen configured height (in pixels)\n 3. {refresh-rate}: Screen configured refresh rate (in Hz)\n 4. {scaled-width}: Screen scaled width (in pixels)\n 5. {scaled-height}: Screen scaled height (in pixels)\n 6. {name}: Screen name\n 7. {type}: Screen type (Built-in or External)\n 8. {rotation}: Screen rotation (in degrees)\n 9. {is-primary}: True if being the primary screen\n 10. {physical-width}: Screen physical width (in millimeters)\n 11. {physical-height}: Screen physical height (in millimeters)\n 12. {inch}: Physical diagonal length in inches\n 13. {ppi}: Pixels per inch (PPI)\n 14. {bit-depth}: Bits per color channel\n 15. {hdr-enabled}: True if high dynamic range (HDR) mode is enabled\n 16. {manufacture-year}: Year of manufacturing\n 17. {manufacture-week}: Nth week of manufacturing in the year\n 18. {serial}: Serial number\n 19. {platform-api}: The platform API used when detecting the display\n 20. {hdr-compatible}: True if the display is HDR compatible\n 21. {scale-factor}: HiDPI scale factor\n 22. {preferred-width}: Screen preferred width (in pixels)\n 23. {preferred-height}: Screen preferred height (in pixels)\n 24. {preferred-refresh-rate}: Screen preferred refresh rate (in Hz)\n 25. {dpi}: DPI",
"description": "Output format for the `Display` module. See Wiki for formatting syntax\n 1. {width}: Screen configured width (in pixels)\n 2. {height}: Screen configured height (in pixels)\n 3. {refresh-rate}: Screen configured refresh rate (in Hz)\n 4. {scaled-width}: Screen scaled width (in pixels)\n 5. {scaled-height}: Screen scaled height (in pixels)\n 6. {name}: Screen name *\n 7. {type}: Screen type (Built-in or External) *\n 8. {rotation}: Screen rotation (in degrees)\n 9. {is-primary}: True if being the primary screen\n 10. {physical-width}: Screen physical width (in millimeters)\n 11. {physical-height}: Screen physical height (in millimeters)\n 12. {inch}: Physical diagonal length in inches\n 13. {ppi}: Pixels per inch (PPI)\n 14. {bit-depth}: Bits per color channel\n 15. {hdr-enabled}: True if high dynamic range (HDR) mode is enabled\n 16. {manufacture-year}: Year of manufacturing\n 17. {manufacture-week}: Nth week of manufacturing in the year\n 18. {serial}: Serial number\n 19. {platform-api}: The platform API used when detecting the display\n 20. {hdr-compatible}: True if the display is HDR compatible\n 21. {scale-factor}: HiDPI scale factor\n 22. {preferred-width}: Screen preferred width (in pixels)\n 23. {preferred-height}: Screen preferred height (in pixels)\n 24. {preferred-refresh-rate}: Screen preferred refresh rate (in Hz)\n 25. {dpi}: DPI",
"type": "string"
},
"diskFormat": {
"description": "Output format for the `Disk` module. See Wiki for formatting syntax\n 1. {size-used}: Size used\n 2. {size-total}: Size total\n 3. {size-percentage}: Size percentage num\n 4. {files-used}: Files used\n 5. {files-total}: Files total\n 6. {files-percentage}: Files percentage num\n 7. {is-external}: True if external volume\n 8. {is-hidden}: True if hidden volume\n 9. {filesystem}: Filesystem\n 10. {name}: Label / name\n 11. {is-readonly}: True if read-only\n 12. {create-time}: Create time in local timezone\n 13. {size-percentage-bar}: Size percentage bar\n 14. {files-percentage-bar}: Files percentage bar\n 15. {days}: Days after creation\n 16. {hours}: Hours after creation\n 17. {minutes}: Minutes after creation\n 18. {seconds}: Seconds after creation\n 19. {milliseconds}: Milliseconds after creation\n 20. {mountpoint}: Mount point / drive letter\n 21. {mount-from}: Mount from (device path)\n 22. {years}: Years integer after creation\n 23. {days-of-year}: Days of year after creation\n 24. {years-fraction}: Years fraction after creation\n 25. {size-free}: Size free\n 26. {size-available}: Size available",
"description": "Output format for the `Disk` module. See Wiki for formatting syntax\n 1. {size-used}: Size used\n 2. {size-total}: Size total\n 3. {size-percentage}: Size percentage num\n 4. {files-used}: Files used\n 5. {files-total}: Files total\n 6. {files-percentage}: Files percentage num\n 7. {is-external}: True if external volume\n 8. {is-hidden}: True if hidden volume\n 9. {filesystem}: Filesystem *\n 10. {name}: Label / name *\n 11. {is-readonly}: True if read-only\n 12. {create-time}: Create time in local timezone\n 13. {size-percentage-bar}: Size percentage bar\n 14. {files-percentage-bar}: Files percentage bar\n 15. {days}: Days after creation\n 16. {hours}: Hours after creation\n 17. {minutes}: Minutes after creation\n 18. {seconds}: Seconds after creation\n 19. {milliseconds}: Milliseconds after creation\n 20. {mountpoint}: Mount point / drive letter *\n 21. {mount-from}: Mount from (device path) *\n 22. {years}: Years integer after creation\n 23. {days-of-year}: Days of year after creation\n 24. {years-fraction}: Years fraction after creation\n 25. {size-free}: Size free\n 26. {size-available}: Size available",
"type": "string"
},
"diskioFormat": {
"description": "Output format for the `DiskIO` module. See Wiki for formatting syntax\n 1. {size-read}: Size of data read [per second] (formatted)\n 2. {size-written}: Size of data written [per second] (formatted)\n 3. {name}: Device name\n 4. {dev-path}: Device raw file path\n 5. {bytes-read}: Size of data read [per second] (in bytes)\n 6. {bytes-written}: Size of data written [per second] (in bytes)\n 7. {read-count}: Number of reads\n 8. {write-count}: Number of writes",
"description": "Output format for the `DiskIO` module. See Wiki for formatting syntax\n 1. {size-read}: Size of data read [per second] (formatted)\n 2. {size-written}: Size of data written [per second] (formatted)\n 3. {name}: Device name *\n 4. {dev-path}: Device raw file path *\n 5. {bytes-read}: Size of data read [per second] (in bytes)\n 6. {bytes-written}: Size of data written [per second] (in bytes)\n 7. {read-count}: Number of reads\n 8. {write-count}: Number of writes",
"type": "string"
},
"dnsFormat": {
@@ -390,7 +393,7 @@
"type": "string"
},
"kernelFormat": {
"description": "Output format for the `Kernel` module. See Wiki for formatting syntax\n 1. {sysname}: Sysname\n 2. {release}: Release\n 3. {version}: Version\n 4. {arch}: Architecture\n 5. {display-version}: Display version\n 6. {page-size}: Page size",
"description": "Output format for the `Kernel` module. See Wiki for formatting syntax\n 1. {sysname}: Sysname\n 2. {release}: Release\n 3. {version}: Version\n 4. {arch}: Architecture\n 5. {page-size}: Page size",
"type": "string"
},
"keyboardFormat": {
@@ -398,7 +401,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": {
@@ -410,7 +413,7 @@
"type": "string"
},
"localipFormat": {
"description": "Output format for the `LocalIp` module. See Wiki for formatting syntax\n 1. {ipv4}: IPv4 address\n 2. {ipv6}: IPv6 address\n 3. {mac}: MAC address\n 4. {ifname}: Interface name\n 5. {is-default-route}: Is default route\n 6. {mtu}: MTU size in bytes\n 7. {speed}: Link speed (formatted)\n 8. {flags}: Interface flags",
"description": "Output format for the `LocalIp` module. See Wiki for formatting syntax\n 1. {ipv4}: IPv4 address\n 2. {ipv6}: IPv6 address\n 3. {mac}: MAC address *\n 4. {ifname}: Interface name *\n 5. {is-default-route}: Is default route\n 6. {mtu}: MTU size in bytes\n 7. {speed}: Link speed (formatted)\n 8. {flags}: Interface flags",
"type": "string"
},
"mediaFormat": {
@@ -422,7 +425,7 @@
"type": "string"
},
"monitorFormat": {
"description": "Output format for the `Monitor` module. See Wiki for formatting syntax\n 1. {name}: Display name\n 2. {width}: Native resolution width in pixels\n 3. {height}: Native resolution height in pixels\n 4. {physical-width}: Physical width in millimeters\n 5. {physical-height}: Physical height in millimeters\n 6. {inch}: Physical diagonal length in inches\n 7. {ppi}: Pixels per inch (PPI)\n 8. {manufacture-year}: Year of manufacturing\n 9. {manufacture-week}: Nth week of manufacturing in the year\n 10. {serial}: Serial number\n 11. {refresh-rate}: Maximum refresh rate in Hz\n 12. {hdr-compatible}: True if the display is HDR compatible",
"description": "Output format for the `Monitor` module. See Wiki for formatting syntax\n 1. {name}: Display name *\n 2. {width}: Native resolution width in pixels\n 3. {height}: Native resolution height in pixels\n 4. {physical-width}: Physical width in millimeters\n 5. {physical-height}: Physical height in millimeters\n 6. {inch}: Physical diagonal length in inches\n 7. {ppi}: Pixels per inch (PPI)\n 8. {manufacture-year}: Year of manufacturing\n 9. {manufacture-week}: Nth week of manufacturing in the year\n 10. {serial}: Serial number\n 11. {refresh-rate}: Maximum refresh rate in Hz\n 12. {hdr-compatible}: True if the display is HDR compatible",
"type": "string"
},
"mouseFormat": {
@@ -430,7 +433,7 @@
"type": "string"
},
"netioFormat": {
"description": "Output format for the `NetIO` module. See Wiki for formatting syntax\n 1. {rx-size}: Size of data received [per second] (formatted)\n 2. {tx-size}: Size of data sent [per second] (formatted)\n 3. {ifname}: Interface name\n 4. {is-default-route}: Is default route\n 5. {rx-bytes}: Size of data received [per second] (in bytes)\n 6. {tx-bytes}: Size of data sent [per second] (in bytes)\n 7. {rx-packets}: Number of packets received [per second]\n 8. {tx-packets}: Number of packets sent [per second]\n 9. {rx-errors}: Number of errors received [per second]\n 10. {tx-errors}: Number of errors sent [per second]\n 11. {rx-drops}: Number of packets dropped when receiving [per second]\n 12. {tx-drops}: Number of packets dropped when sending [per second]",
"description": "Output format for the `NetIO` module. See Wiki for formatting syntax\n 1. {rx-size}: Size of data received [per second] (formatted)\n 2. {tx-size}: Size of data sent [per second] (formatted)\n 3. {ifname}: Interface name *\n 4. {is-default-route}: Is default route\n 5. {rx-bytes}: Size of data received [per second] (in bytes)\n 6. {tx-bytes}: Size of data sent [per second] (in bytes)\n 7. {rx-packets}: Number of packets received [per second]\n 8. {tx-packets}: Number of packets sent [per second]\n 9. {rx-errors}: Number of errors received [per second]\n 10. {tx-errors}: Number of errors sent [per second]\n 11. {rx-drops}: Number of packets dropped when receiving [per second]\n 12. {tx-drops}: Number of packets dropped when sending [per second]",
"type": "string"
},
"openclFormat": {
@@ -442,15 +445,15 @@
"type": "string"
},
"osFormat": {
"description": "Output format for the `OS` module. See Wiki for formatting syntax\n 1. {sysname}: Name of the kernel\n 2. {name}: Name of the OS\n 3. {pretty-name}: Pretty name of the OS, if available\n 4. {id}: ID of the OS\n 5. {id-like}: ID like of the OS\n 6. {variant}: Variant of the OS\n 7. {variant-id}: Variant ID of the OS\n 8. {version}: Version of the OS\n 9. {version-id}: Version ID of the OS\n 10. {codename}: Version codename of the OS\n 11. {build-id}: Build ID of the OS\n 12. {arch}: Architecture of the OS",
"description": "Output format for the `OS` module. See Wiki for formatting syntax\n 1. {sysname}: Name of the kernel *\n 2. {name}: Name of the OS *\n 3. {pretty-name}: Pretty name of the OS, if available\n 4. {id}: ID of the OS\n 5. {id-like}: ID like of the OS\n 6. {variant}: Variant of the OS\n 7. {variant-id}: Variant ID of the OS\n 8. {version}: Version of the OS\n 9. {version-id}: Version ID of the OS\n 10. {codename}: Version codename of the OS\n 11. {build-id}: Build ID of the OS\n 12. {arch}: Architecture of the OS\n 13. {kernel-release}: Release of the kernel",
"type": "string"
},
"packagesFormat": {
"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. {rum}: Number of rum overlay packages\n 43. {scoop-global}: Number of scoop-global packages\n 44. {scoop-user}: Number of scoop-user packages\n 45. {snap}: Number of snap packages\n 46. {soar}: Number of soar packages\n 47. {sorcery}: Number of sorcery packages\n 48. {winget}: Number of winget packages\n 49. {xbps}: Number of xbps packages\n 50. {brew-all}: Total number of all brew packages\n 51. {flatpak-all}: Total number of all flatpak app packages\n 52. {guix-all}: Total number of all guix packages\n 53. {hpkg-all}: Total number of all hpkg packages\n 54. {nix-all}: Total number of all nix packages\n 55. {all}: Number of all packages",
"type": "string"
},
"physicaldiskFormat": {
"description": "Output format for the `PhysicalDisk` module. See Wiki for formatting syntax\n 1. {size}: Device size (formatted)\n 2. {name}: Device name\n 3. {interconnect}: Device interconnect type\n 4. {dev-path}: Device raw file path\n 5. {serial}: Serial number\n 6. {physical-type}: Device kind (SSD or HDD)\n 7. {removable-type}: Device kind (Removable or Fixed)\n 8. {readonly-type}: Device kind (Read-only or Read-write)\n 9. {revision}: Product revision\n 10. {temperature}: Device temperature (formatted)",
"description": "Output format for the `PhysicalDisk` module. See Wiki for formatting syntax\n 1. {size}: Device size (formatted)\n 2. {name}: Device name *\n 3. {interconnect}: Device interconnect type\n 4. {dev-path}: Device raw file path *\n 5. {serial}: Serial number\n 6. {physical-type}: Device kind (SSD or HDD)\n 7. {removable-type}: Device kind (Removable or Fixed)\n 8. {readonly-type}: Device kind (Read-only or Read-write)\n 9. {revision}: Product revision\n 10. {temperature}: Device temperature (formatted)",
"type": "string"
},
"physicalmemoryFormat": {
@@ -462,11 +465,11 @@
"type": "string"
},
"poweradapterFormat": {
"description": "Output format for the `PowerAdapter` module. See Wiki for formatting syntax\n 1. {watts}: Power adapter watts\n 2. {name}: Power adapter name\n 3. {manufacturer}: Power adapter manufacturer\n 4. {model}: Power adapter model\n 5. {description}: Power adapter description\n 6. {serial}: Power adapter serial number",
"description": "Output format for the `PowerAdapter` module. See Wiki for formatting syntax\n 1. {watts}: Power adapter watts\n 2. {name}: Power adapter name\n 3. {manufacturer}: Power adapter manufacturer\n 4. {model-name}: Power adapter model\n 5. {description}: Power adapter description\n 6. {serial}: Power adapter serial number",
"type": "string"
},
"processesFormat": {
"description": "Output format for the `Processes` module. See Wiki for formatting syntax\n 1. {result}: Process count",
"description": "Output format for the `Processes` module. See Wiki for formatting syntax\n 1. {result}: Process count\n 2. {threads}: Thread count",
"type": "string"
},
"publicipFormat": {
@@ -482,7 +485,7 @@
"type": "string"
},
"swapFormat": {
"description": "Output format for the `Swap` module. See Wiki for formatting syntax\n 1. {used}: Used size\n 2. {total}: Total size\n 3. {percentage}: Percentage used (num)\n 4. {percentage-bar}: Percentage used (bar)\n 5. {name}: Name",
"description": "Output format for the `Swap` module. See Wiki for formatting syntax\n 1. {used}: Used size\n 2. {total}: Total size\n 3. {percentage}: Percentage used (num)\n 4. {percentage-bar}: Percentage used (bar)\n 5. {name}: Name *",
"type": "string"
},
"terminalFormat": {
@@ -505,6 +508,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 +549,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": {
@@ -550,7 +557,7 @@
"type": "string"
},
"zpoolFormat": {
"description": "Output format for the `Zpool` module. See Wiki for formatting syntax\n 1. {name}: Zpool name\n 2. {guid}: Zpool guid\n 3. {state}: Zpool state\n 4. {size-used}: Size used\n 5. {size-allocated}: Size allocated\n 6. {size-total}: Size total\n 7. {used-percentage}: Size used percentage num\n 8. {allocated-percentage}: Size allocated percentage num\n 9. {frag-percentage}: Fragmentation percentage num\n 10. {used-percentage-bar}: Size used percentage bar\n 11. {allocated-percentage-bar}: Size allocated percentage bar\n 12. {frag-percentage-bar}: Fragmentation percentage bar\n 13. {is-readonly}: Is read-only",
"description": "Output format for the `Zpool` module. See Wiki for formatting syntax\n 1. {name}: Zpool name *\n 2. {guid}: Zpool guid *\n 3. {state}: Zpool state\n 4. {size-used}: Size used\n 5. {size-allocated}: Size allocated\n 6. {size-total}: Size total\n 7. {used-percentage}: Size used percentage num\n 8. {allocated-percentage}: Size allocated percentage num\n 9. {frag-percentage}: Fragmentation percentage num\n 10. {used-percentage-bar}: Size used percentage bar\n 11. {allocated-percentage-bar}: Size allocated percentage bar\n 12. {frag-percentage-bar}: Fragmentation percentage bar\n 13. {is-readonly}: Is read-only",
"type": "string"
}
},
@@ -762,21 +769,36 @@
"description": "Whether to preserve the aspect ratio of the logo. Supported by iTerm image protocol only",
"default": false
},
"recache": {
"type": "boolean",
"description": "If true, regenerate image logo cache",
"default": false
"cache": {
"description": "How the image logo cache is used. true reuses a cached rendering when it is valid and writes it back on a cache miss; false ignores the cache completely, neither reading nor writing it; \"regen\" ignores any existing cached rendering and regenerates it",
"oneOf": [
{
"type": "boolean",
"description": "Whether to use the image logo cache"
},
{
"const": "regen",
"description": "Regenerate the image logo cache, ignoring any existing cached rendering"
}
],
"default": true
},
"position": {
"type": "string",
"description": "Position of the logo",
"description": "Position of the logo. Auto places a left-side logo above the output when the terminal is too narrow to reserve 32 columns for module output.",
"enum": [
"auto",
"left",
"top",
"right"
],
"default": "left"
},
"animationFrame": {
"type": "integer",
"description": "Frame of an animated image to render:\n0 plays the animation (kitty image protocol only), a positive value is the 1-based frame number, and a negative value counts back from the end, so -1 is the last frame.\nOnly the JSON config can express a negative value, as the command line parser reads a leading `-` as another option",
"default": 1
},
"chafa": {
"type": "object",
"additionalProperties": false,
@@ -916,6 +938,17 @@
"type": "boolean",
"description": "Whether to detect and display component versions. Mainly for benchmarking",
"default": true
},
"preload": {
"description": "Scripts to run while the config is read, before any module runs",
"type": "object",
"additionalProperties": false,
"properties": {
"lua": {
"type": "string",
"description": "Lua script to run once, as soon as the config is read, for example `dofile('/path/to/helpers.lua')`"
}
}
}
}
},
@@ -1057,6 +1090,121 @@
"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": "fi",
"description": "Finnish"
},
{
"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"
}
}
},
@@ -1448,6 +1596,7 @@
"command",
"colors",
"cursor",
"custom",
"datetime",
"display",
"disk",
@@ -1467,6 +1616,7 @@
"loadavg",
"locale",
"localip",
"logo",
"media",
"memory",
"monitor",
@@ -1482,6 +1632,7 @@
"poweradapter",
"processes",
"publicip",
"top",
"separator",
"shell",
"sound",
@@ -1615,6 +1766,24 @@
"type": "boolean",
"default": false
},
"showType": {
"description": "Which Bluetooth stacks to look for",
"oneOf": [
{
"const": "classic",
"description": "Look for BR/EDR devices only"
},
{
"const": "le",
"description": "Look for Bluetooth Low Energy devices only"
},
{
"const": "both",
"description": "Look for devices on either stack"
}
],
"default": "both"
},
"percent": {
"$ref": "#/$defs/percent"
},
@@ -1750,7 +1919,7 @@
"$ref": "#/$defs/percent"
},
"ddcciSleep": {
"description": "Sleep time in milliseconds between DDC/CI requests\nSet to null to skip DDC/CI detection\nSee <https://www.ddcutil.com/performance_options/#option-sleep-multiplier> for details",
"description": "Sleep time in milliseconds between DDC/CI requests\nSet to null to skip DDC/CI detection",
"oneOf": [
{
"type": "null",
@@ -1971,7 +2140,7 @@
"showPeCoreCount": {
"description": "Whether to detect and display CPU frequencies for different core types (for example, P-cores and E-cores), if supported",
"type": "boolean",
"default": false
"default": true
},
"key": {
"$ref": "#/$defs/key"
@@ -2005,8 +2174,10 @@
"const": "cpucache",
"description": "Print CPU cache sizes"
},
"percent": {
"$ref": "#/$defs/percent"
"compact": {
"type": "boolean",
"description": "Print one line with a total per cache level instead of one line per level",
"default": false
},
"key": {
"$ref": "#/$defs/key"
@@ -2125,6 +2296,7 @@
"description": "Set the number of white spaces to print before the symbol",
"type": "integer",
"minimum": 0,
"maximum": 4294967295,
"default": 0
},
"block": {
@@ -2136,6 +2308,7 @@
"description": "Set the block width in spaces",
"type": "integer",
"minimum": 1,
"maximum": 9,
"default": 3
},
"range": {
@@ -2193,7 +2366,7 @@
"additionalProperties": false,
"properties": {
"type": {
"description": "Run a custom shell command",
"description": "Run custom shell scripts",
"const": "command"
},
"shell": {
@@ -2255,9 +2428,6 @@
"const": "cursor",
"description": "Print cursor style name"
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
@@ -2291,8 +2461,7 @@
"const": "custom"
},
"key": {
"description": "Leave empty to hide the key",
"type": "string"
"$ref": "#/$defs/key"
},
"keyColor": {
"$ref": "#/$defs/keyColor"
@@ -2324,9 +2493,6 @@
"const": "datetime",
"description": "Print the current date and time"
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
@@ -2585,8 +2751,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": 250,
"minimum": 1
},
"key": {
@@ -2703,9 +2869,6 @@
"const": "editor",
"description": "Print information about the default editor (`$VISUAL` or `$EDITOR`)"
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
@@ -2738,9 +2901,6 @@
"const": "font",
"description": "Print system font names"
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
@@ -3486,8 +3646,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": 250,
"minimum": 1
},
"key": {
@@ -3646,18 +3806,24 @@
"enum": [
"am",
"apk",
"appimage",
"brew",
"cards",
"choco",
"crux",
"dpkg",
"emerge",
"eopkg",
"flatpak",
"guix",
"hpkg",
"installrelease",
"kiss",
"linglong",
"lpkg",
"lpkgbuild",
"macports",
"moss",
"mport",
"nix",
"opkg",
@@ -3666,10 +3832,14 @@
"paludis",
"pisi",
"pkg",
"pkgsrc",
"pkgtool",
"porg",
"rpm",
"rum",
"scoop",
"snap",
"soar",
"sorcery",
"winget",
"xbps"
@@ -3681,8 +3851,7 @@
"description": "Enable all package managers",
"type": "null"
}
],
"default": ["winget"]
]
},
"combined": {
"description": "Whether to combine related package managers into single counts (e.g., nix-system + nix-user = nix)",
@@ -3869,7 +4038,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"
@@ -3904,10 +4078,9 @@
"const": "publicip"
},
"url": {
"description": "URL of the public IP detection server to use. Only HTTP is supported",
"description": "URL of the public IP detection server to use. Only HTTP is supported\nIf empty, `http://ipinfo.io/json` is requested (`http://v6.ipinfo.io/json` when `ipv6` is set), with TCP Fast Open enabled. A non-empty URL disables both and makes `ipv6` have no effect",
"type": "string",
"format": "url",
"default": "http://ipinfo.io/ip"
"format": "url"
},
"timeout": {
"description": "Time in milliseconds to wait for the public IP server to respond\nSet to 0 to disable the timeout",
@@ -3965,6 +4138,7 @@
"description": "Number of times to repeat the separator string, or 0 to auto-detect",
"type": "integer",
"minimum": 0,
"maximum": 4294967295,
"default": 0
},
"condition": {
@@ -4314,6 +4488,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": 250
},
"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",
@@ -4572,6 +4843,9 @@
"const": "wifi",
"description": "Print connected Wi-Fi info (SSID, connection and security protocol)"
},
"percent": {
"$ref": "#/$defs/percent"
},
"key": {
"$ref": "#/$defs/key"
},
+6 -5
View File
@@ -99,17 +99,18 @@
"gamepad",
"mouse",
"keyboard",
{
"type": "weather",
"timeout": 1000
},
"tpm",
"netio",
"diskio",
{
"type": "physicaldisk",
"temp": true
},
"tpm",
{
"type": "weather",
"timeout": 1000
},
"top",
"version",
"break",
"colors"
+8 -7
View File
@@ -94,24 +94,25 @@
"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
},
"tpm",
"netio",
"diskio",
{
"type": "physicaldisk",
"temp": true
},
"tpm",
{
"type": "weather",
"timeout": 1000
},
"top",
"version",
"logo",
"break",
+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}",
+9 -2
View File
@@ -90,7 +90,7 @@
{
"type": "disk",
"key": "║{#cyan}│ {icon} Disk │{$4}│{#keys}║{$2}",
"format": "{size-used} \/ {size-total} ({size-percentage}) - {filesystem}",
"format": "{mountpoint} {size-used} \/ {size-total} ({size-percentage}) - {filesystem}",
},
{
"type": "battery",
@@ -195,7 +195,7 @@
"keyIcon": "",
"key": "║{#red}│ {icon} Clang │{$4}│{#keys}║{$2}",
"text": "clang --version | findstr version", // Finds the line with "version"
"format": "clang {~-6}" // Prints the last 6 characters (version number)
"format": "clang {~14,20}" // Prints 14~20 characters (version number)
},
{
"type": "command",
@@ -204,6 +204,13 @@
"text": "node --version",
"format": "node {~1}" // {~1} removes first character (v)
},
{
"type": "command",
"keyIcon": "",
"key": "║{#red}│ {icon} Python │{$4}│{#keys}║{$2}",
"text": "python --version",
"format": "python {~7}" // {~7} removes first 7 characters ("Python" with extra space)
},
{
"type": "command",
"keyIcon": "",
+12 -3
View File
@@ -38,8 +38,14 @@
"compactType": "original",
"key": "Resolution"
},
"de",
"wm",
{
"type": "de",
"key": "DE"
},
{
"type": "wm",
"key": "WM"
},
"wmtheme",
"theme",
"icons",
@@ -48,7 +54,10 @@
"type": "terminalfont",
"format": "{/name}{-}{/}{name}{?size} {size}{?}"
},
"cpu",
{
"type": "cpu",
"showPeCoreCount": false
},
{
"type": "gpu",
"key": "GPU",
+18
View File
@@ -0,0 +1,18 @@
Copyright (c) 2014-2016 Hayaki Saito
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+13
View File
@@ -0,0 +1,13 @@
src/quant.c is derived from ppmquant, originally by Jef Poskanzer.
Copyright (C) 1989, 1991 by Jef Poskanzer.
Copyright (C) 2001 by Bryan Henderson.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted, provided
that the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation. This software is provided "as is" without express or
implied warranty.
+49
View File
@@ -0,0 +1,49 @@
"sixel" license (original license)
==================================
src/tosixel.c and src/fromsixel.c are derived from "sixel" original version (2014-3-2)
Package: http://nanno.dip.jp/softlib/man/rlogin/sixel.tar.gz
Unofficial repo: https://github.com/saitoha/sixel
This work is written by kmiya@ culti. He distributes it under very permissive license.
The original license text(in Japanese) is:
```
このプログラム及びソースコードの使用について個人・商用を問わず
ご自由に使用していただいで結構です。
また、配布・転載・紹介もご連絡の必要もありません。
ソースの改変による配布も自由ですが、どのバージョンの改変かを
明記されることを希望します。
バージョン情報が無い場合は、配布物の年月日を明記されることを
希望します。
2014/10/05 kmiya
```
The unofficial translation:
```
Anyone is free to use this program for any purpose,
commercial or non-commercial, without any restriction.
Anyone is free to distribute, copy, publish, or
advertise this software, without any contact.
Anyone is free to distribute with modification of the
source code, but I "hope" that its based version or
date will be written clearly.
2014/10/05 kmiya
```
kmiya also said this is compatible with MIT/BSD/GPL.
+218
View File
@@ -0,0 +1,218 @@
# libsixel (encoder subset, vendored)
Encoder-only subset of [libsixel](https://github.com/saitoha/libsixel) `1.8.7-r2`,
used by `src/logo/image/` to render the `sixel` logo type **on Windows and macOS**.
## Why this is vendored
Neither MSYS2 nor vcpkg packages libsixel, so on Windows there is nothing to
`dlopen()` or to link against. The encoder is small and has no external
dependencies, so the necessary files are embedded instead. The intent is to upstream
a libsixel package to MSYS2 later and switch to it; until then this directory is the
source of truth.
macOS *does* have a package — Homebrew's `libsixel` is this very version, `1.8.7-r2`
(`brew info libsixel`) — but it is deliberately not used. It drags in `jpeg-turbo` /
`libpng` for the decoder side we never call, and linking it would mean a different
encoder on each platform. Since Windows needs an embedded copy regardless, one shared
copy is simpler than two.
## Why only Windows and macOS
Windows loses ImageMagick entirely — its image backend moves to WIC, which decodes
and scales but cannot *encode* sixel — so it needs an encoder of its own.
macOS does not get ImageMagick either: `ENABLE_IMAGEMAGICK6` / `ENABLE_IMAGEMAGICK7`
are gated on `LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR Haiku OR
GNU`, with no `APPLE`, and its image backend is ImageIO. So it shares the same
requirement.
On every other platform ImageMagick stays, and it already produces sixel through
its own SIXEL coder. Adding libsixel there would mean a second encoder for a
capability that already works: one more dependency for zero gain. So `ENABLE_SIXEL`
is gated on `WIN32 OR APPLE`, and these sources are **not compiled at all** on
Linux / the BSDs.
Consequence: sixel bytes differ between Windows / macOS (libsixel) and the rest
(ImageMagick). That is deliberate and accepted — see
`doc/windows-image-backend.md` decision 9.
## Transparency
The subset is used for its keycolor support. `sixel_dither_set_transparent()` marks a
palette index as the transparent one; `output_rgb_palette_definition()` then leaves that
entry out of the palette definition, and `sixel_encode_body()` never puts a keycolor
pixel into the node map, so nothing is drawn there and the terminal background shows
through. `src/logo/image/sixel.c` drives this for sources that contain fully transparent
pixels — it maps those pixels onto a dedicated index one past the quantized palette,
because keying a *content* colour would also drop every opaque pixel that shares it.
Two things have to line up for the background to actually be transparent: the caller has to
mark a keycolor, so the keyed pixels are never painted (above), **and** the encoder has to ask
for a transparent background, so the terminal leaves those unpainted pixels alone instead of
filling them. The second half is the DCS `P2` parameter — see
[Local modifications](#local-modifications) item 3.
Note what this is and is not: it is a **palette-index** keycolor, not alpha support. A
partially transparent pixel has to be drawn in its unblended colour, since a sixel has
no way to blend with whatever the terminal has behind it.
⚠️ Do not vendor a *newer* libsixel in the belief that this version cannot do
transparency. It can, and it was measured: the official `1.8.7-r2` omits the keycolor
entry and leaves the keyed pixels unpainted, with the remaining pixels correctly placed.
When a transparent background is not showing up, check the caller first — historically
the caller simply never called `sixel_dither_set_transparent()`, and an image logo only
renders correctly once it does. If the caller *is* setting a keycolor and the background is
still solid, check `P2` instead: the keyed pixels are being left unpainted correctly, but the
terminal is filling them.
## What was kept
Starting from `sixel_dither_new` / `sixel_dither_initialize` / `sixel_output_new` /
`sixel_encode` and following the dependency closure, only these files are reachable:
| File | Provides |
|---|---|
| `tosixel.c` | `sixel_encode`, `sixel_putc`, `sixel_node_*`, DCS envelope handling |
| `dither.c` / `dither.h` | `sixel_dither_new`, `sixel_dither_initialize`, `sixel_dither_unref` |
| `output.c` / `output.h` | `sixel_output_new`, `sixel_output_unref` |
| `quant.c` / `quant.h` | Median-cut palette construction |
| `pixelformat.c` | `sixel_helper_compute_depth`, `sixel_helper_normalize_pixelformat` |
| `allocator.c` / `allocator.h` | `sixel_allocator_*` |
| `status.c` / `status.h` | `sixel_helper_set_additional_message` |
| `malloc_stub.h` | Included by `allocator.c`; compiles to nothing when `HAVE_MALLOC` |
| `sixel.h` | Public API (upstream `include/sixel.h`) |
## What was removed
Everything else in the upstream tree, in particular:
- **Decoding**: `decoder.c`, `fromsixel.c`, `frompnm.c`, `fromgif.c`, `frame.c`,
`loader.c`, `stb_image.h`, `stb_image_write.*`. Image decoding is done by the
platform backend (WIC on Windows, ImageIO on macOS, ImageMagick elsewhere), so
libsixel only ever sees an RGBA8 buffer here.
- **The `sixel_encoder_*` / `sixel_decoder_*` high-level API**: `encoder.c`,
`writer.c`, `tty.c`, `scale.c`. These pull in file I/O, terminal probing and the
loader registry. We call the dither/output/encode trio directly.
- **`rgblookup.h` / `rgblookup.gperf`**: a gperf-generated color-name table used
only by `encoder.c` for `--builtin-palette` parsing.
- **`converters/`** (`img2sixel`, `sixel2png`), **`python/`**, **`tools/`**,
**`tests/`**, **`images/`** (216 test images), and the packaging templates
(`package.json.in.in`, `libsixel.pc.in`).
- **The autotools machinery**: `configure`, `Makefile.in`, `aclocal.m4`, `m4/`,
`ltmain.sh`, `config.h.in`, … See item 2 under
[Local modifications](#local-modifications).
- **Six of the nine `LICENSE.*` files**: `LICENSE.images` / `.mesa` / `.pngsuite` /
`.sdump` / `.stb` cover only material that was dropped (test images, the OpenGL
example, the `sdump` tool, stb). The three that remain are the complete set
required by the files above — verified against each file's own header, see
[License](#license).
Result: **20 files / 256 KB**, down from 319 files / 11.3 MB. Note that 8.0 MB of
that 11.3 MB was `images/` fixtures, so the meaningful comparison is code:
`src/` + `include/` went from 50 files / 1.17 MB to 20 files / 256 KB.
## Local modifications
Four deliberate divergences from upstream. Re-apply all four when re-syncing.
1. **`sixel.h` — `SIXELAPI` is empty.** Upstream defines it as
`__declspec(dllexport)` on Windows. This subset is compiled into `libfastfetch`
statically rather than into a standalone DLL, so the attribute would (a) add
every libsixel symbol to fastfetch's export table and (b) make `sixel.h`'s
declarations disagree with the ones in `dither.h` / `output.h` / `quant.h` /
`allocator.h`, which do not carry it (clang warns:
`-Wdll-attribute-on-redeclaration`).
2. **`config.h` is hand-written.** Upstream generates it with `configure`. Only
these vendored files read it, and they reference a small fixed set of macros,
so the values are hard-coded. Two things to watch:
- `HAVE_TESTS` must stay **undefined**, not `0`: `sixel.h` gates its test-only
declarations with `#ifdef HAVE_TESTS`, so `#define HAVE_TESTS 0` would still
pull in declarations for functions we do not compile.
- `HAVE_MEMORY_H` / `HAVE_STRING_H` must be `1`. fastfetch builds with
`-Werror=implicit-function-declaration`, so a missing `<memory.h>` /
`<string.h>` include in `pixelformat.c` / `status.c` is a hard error.
3. **`tosixel.c` — the DCS `P2` parameter is initialised to 1.** `sixel_encode_header()`
declares `int p[3] = {0, 0, 0}`, and the trailing-zero trimming right below it then drops
all three, so the envelope is a bare `ESC P q`. That means `P2 = 0`, which tells the
terminal to fill every unset pixel with **colour-table entry 0** — and that entry is
defined by the image's own palette, so a keycolor image comes out with a solid rectangle
behind it rather than transparency. Setting `p[1] = 1` leaves unset pixels untouched.
The trimming still drops the trailing aspect-ratio parameter, so the envelope is
`ESC P 0;1 q`. `P3` defaults to 0 and the two spellings are equivalent — verified by
decoding both with libsixel's own decoder and with ImageMagick's. ImageMagick's SIXEL
coder and chafa both write `0;1;0` explicitly.
This only shows up on a terminal that implements the VT340 background rule. Windows
Terminal does (`_backgroundFillRequired = (_conformanceLevel == 1 || !transparent)` in
`src/terminal/adapter/SixelParser.cpp`), which is where it was reproduced; WezTerm does
not fill, so the identical bytes look correct there. It is also why Linux never showed
the problem: that path goes through ImageMagick, which already sends `P2 = 1`.
4. **`output.c` — release the allocator reference if output allocation fails.**
`sixel_output_new()` creates an allocator or takes an additional reference before
allocating the output context. If that allocation fails, the reference is not owned by
an output object and must be released before returning `SIXEL_BAD_ALLOCATION`.
Fastfetch passes a null allocator, so without this cleanup the newly created allocator
becomes unreachable on this error path. The successful path and returned error code are
unchanged.
The vendored sources are also exempted from `-Wconversion` in `CMakeLists.txt`:
upstream has ~160 implicit int→`unsigned char` narrowing warnings. Everything else
(`-Wall -Wextra` and fastfetch's `-Werror=` set) applies unchanged, and the subset
compiles clean.
## Re-syncing with upstream
```sh
VER=1.8.7-r2
git clone --depth 1 --branch "$VER" https://github.com/saitoha/libsixel /tmp/libsixel
# 1. copy the closure (paths change from src/foo.c to foo.c, include/sixel.h to sixel.h)
for f in tosixel.c dither.c dither.h output.c output.h quant.c quant.h \
pixelformat.c allocator.c allocator.h status.c status.h malloc_stub.h; do
cp "/tmp/libsixel/src/$f" .
done
cp /tmp/libsixel/include/sixel.h .
cp /tmp/libsixel/LICENSE /tmp/libsixel/LICENSE.sixel /tmp/libsixel/LICENSE.pnmcolormap .
# 2. re-apply the four local modifications listed above
# 3. verify
```
Verification (must be 0 diagnostics, and the encoder must emit a DCS-wrapped stream).
Both platforms compile this subset, so run it on both:
```sh
# Windows: CC=/c/msys64/clang64/bin/cc.exe
# macOS: CC=cc (verified with Apple clang 21: 0 diagnostics)
CC=cc
FLAGS="-I. -Wall -Wextra -Wconversion -Wno-conversion -Werror=uninitialized \
-Werror=return-type -Werror=vla -Werror=incompatible-pointer-types \
-Werror=implicit-function-declaration -Werror=int-conversion -std=gnu23"
for f in tosixel dither output quant allocator pixelformat status; do
$CC $FLAGS -c $f.c -o /dev/null || echo "FAILED: $f"
done
```
If upstream adds a call from the closure to a new file, the build fails at link
time with an undefined `sixel_*` symbol — add that file and repeat.
## License
MIT-compatible, but three separate notices apply and all three files must be kept.
Mapping verified against each source file's own copyright header:
| File | Applies to |
|---|---|
| `LICENSE` | MIT, Copyright (c) 2014-2016 Hayaki Saito — `dither.c`, `output.c`, `pixelformat.c`, `allocator.c`, `status.c` (their headers say 2014-2018/2019; same MIT terms) |
| `LICENSE.sixel` | `tosixel.c` — derived from kmiya's original `sixel` (2014-3-2), permissive, re-licensed MIT by Hayaki Saito |
| `LICENSE.pnmcolormap` | `quant.c` — derived from `ppmquant` by Jef Poskanzer / Bryan Henderson |
Upstream also ships `LICENSE.images`, `.mesa`, `.pngsuite`, `.sdump` and `.stb`;
none of them cover a file in this subset, so they were not carried over. If a
re-sync ever pulls in a file from the decoder side (`fromgif.c` → `.stb`,
`fromsixel.c` → `.sixel`), re-check this table.
+379
View File
@@ -0,0 +1,379 @@
/*
* Copyright (c) 2014-2018 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
/* STDC_HEADERS */
#include <stdlib.h>
#if HAVE_ASSERT_H
# include <assert.h>
#endif /* HAVE_ASSERT_H */
#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#if HAVE_ERRNO_H
# include <errno.h>
#endif /* HAVE_ERRNO_H */
#if HAVE_MEMORY_H
# include <memory.h>
#endif /* HAVE_MEMORY_H */
#include "allocator.h"
#include "malloc_stub.h"
/* create allocator object */
SIXELSTATUS
sixel_allocator_new(
sixel_allocator_t /* out */ **ppallocator, /* allocator object to be created */
sixel_malloc_t /* in */ fn_malloc, /* custom malloc() function */
sixel_calloc_t /* in */ fn_calloc, /* custom calloc() function */
sixel_realloc_t /* in */ fn_realloc, /* custom realloc() function */
sixel_free_t /* in */ fn_free) /* custom free() function */
{
SIXELSTATUS status = SIXEL_FALSE;
if (ppallocator == NULL) {
sixel_helper_set_additional_message(
"sixel_allocator_new: given argument ppallocator is null.");
status = SIXEL_BAD_ARGUMENT;
goto end;
}
if (fn_malloc == NULL) {
fn_malloc = malloc;
}
if (fn_calloc == NULL) {
fn_calloc = calloc;
}
if (fn_realloc == NULL) {
fn_realloc = realloc;
}
if (fn_free == NULL) {
fn_free = free;
}
*ppallocator = fn_malloc(sizeof(sixel_allocator_t));
if (*ppallocator == NULL) {
sixel_helper_set_additional_message(
"sixel_allocator_new: fn_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
(*ppallocator)->ref = 1;
(*ppallocator)->fn_malloc = fn_malloc;
(*ppallocator)->fn_calloc = fn_calloc;
(*ppallocator)->fn_realloc = fn_realloc;
(*ppallocator)->fn_free = fn_free;
status = SIXEL_OK;
end:
return status;
}
/* destruct allocator object */
static void
sixel_allocator_destroy(
sixel_allocator_t /* in */ *allocator) /* allocator object to
be destroyed */
{
/* precondition */
assert(allocator);
assert(allocator->fn_free);
allocator->fn_free(allocator);
}
/* increase reference count of allocatort object (thread-unsafe) */
SIXELAPI void
sixel_allocator_ref(
sixel_allocator_t /* in */ *allocator) /* allocator object to be
increment reference counter */
{
/* precondition */
assert(allocator);
/* TODO: be thread safe */
++allocator->ref;
}
/* decrease reference count of output context object (thread-unsafe) */
SIXELAPI void
sixel_allocator_unref(
sixel_allocator_t /* in */ *allocator) /* allocator object to be unreference */
{
/* TODO: be thread safe */
if (allocator) {
assert(allocator->ref > 0);
--allocator->ref;
if (allocator->ref == 0) {
sixel_allocator_destroy(allocator);
}
}
}
/* call custom malloc() */
SIXELAPI void *
sixel_allocator_malloc(
sixel_allocator_t /* in */ *allocator, /* allocator object */
size_t /* in */ n) /* allocation size */
{
/* precondition */
assert(allocator);
assert(allocator->fn_malloc);
if (n == 0) {
sixel_helper_set_additional_message(
"sixel_allocator_malloc: called with n == 0");
return NULL;
}
if (n > SIXEL_ALLOCATE_BYTES_MAX) {
return NULL;
}
return allocator->fn_malloc(n);
}
/* call custom calloc() */
SIXELAPI void *
sixel_allocator_calloc(
sixel_allocator_t /* in */ *allocator, /* allocator object */
size_t /* in */ nelm, /* number of elements */
size_t /* in */ elsize) /* size of element */
{
size_t n;
/* precondition */
assert(allocator);
assert(allocator->fn_calloc);
n = nelm * elsize;
if (n == 0) {
sixel_helper_set_additional_message(
"sixel_allocator_malloc: called with n == 0");
return NULL;
}
if (n > SIXEL_ALLOCATE_BYTES_MAX) {
return NULL;
}
return allocator->fn_calloc(nelm, elsize);
}
/* call custom realloc() */
SIXELAPI void *
sixel_allocator_realloc(
sixel_allocator_t /* in */ *allocator, /* allocator object */
void /* in */ *p, /* existing buffer to be re-allocated */
size_t /* in */ n) /* re-allocation size */
{
/* precondition */
assert(allocator);
assert(allocator->fn_realloc);
if (n == 0) {
sixel_helper_set_additional_message(
"sixel_allocator_malloc: called with n == 0");
return NULL;
}
if (n > SIXEL_ALLOCATE_BYTES_MAX) {
return NULL;
}
return allocator->fn_realloc(p, n);
}
/* call custom free() */
SIXELAPI void
sixel_allocator_free(
sixel_allocator_t /* in */ *allocator, /* allocator object */
void /* in */ *p) /* existing buffer to be freed */
{
/* precondition */
assert(allocator);
assert(allocator->fn_free);
allocator->fn_free(p);
}
#if HAVE_TESTS
volatile int sixel_debug_malloc_counter;
void *
sixel_bad_malloc(size_t size)
{
return sixel_debug_malloc_counter-- == 0 ? NULL: malloc(size);
}
void *
sixel_bad_calloc(size_t count, size_t size)
{
(void) count;
(void) size;
return NULL;
}
void *
sixel_bad_realloc(void *ptr, size_t size)
{
(void) ptr;
(void) size;
return NULL;
}
#endif /* HAVE_TESTS */
#if 0
int
rpl_posix_memalign(void **memptr, size_t alignment, size_t size)
{
#if HAVE_POSIX_MEMALIGN
return posix_memalign(memptr, alignment, size);
#elif HAVE_ALIGNED_ALLOC
*memptr = aligned_alloc(alignment, size);
return *memptr ? 0: ENOMEM;
#elif HAVE_MEMALIGN
*memptr = memalign(alignment, size);
return *memptr ? 0: ENOMEM;
#elif HAVE__ALIGNED_MALLOC
return _aligned_malloc(size, alignment);
#else
# error
#endif /* _MSC_VER */
}
#endif
#if HAVE_TESTS
static int
test1(void)
{
int nret = EXIT_FAILURE;
SIXELSTATUS status;
sixel_allocator_t *allocator = NULL;
status = sixel_allocator_new(NULL, malloc, calloc, realloc, free);
if (status != SIXEL_BAD_ARGUMENT) {
goto error;
}
status = sixel_allocator_new(&allocator, NULL, calloc, realloc, free);
if (SIXEL_FAILED(status)) {
goto error;
}
status = sixel_allocator_new(&allocator, malloc, NULL, realloc, free);
if (SIXEL_FAILED(status)) {
goto error;
}
status = sixel_allocator_new(&allocator, malloc, calloc, NULL, free);
if (SIXEL_FAILED(status)) {
goto error;
}
status = sixel_allocator_new(&allocator, malloc, calloc, realloc, NULL);
if (SIXEL_FAILED(status)) {
goto error;
}
nret = EXIT_SUCCESS;
error:
return nret;
}
static int
test2(void)
{
int nret = EXIT_FAILURE;
SIXELSTATUS status;
sixel_allocator_t *allocator = NULL;
sixel_debug_malloc_counter = 1;
status = sixel_allocator_new(&allocator, sixel_bad_malloc, calloc, realloc, free);
if (status == SIXEL_BAD_ALLOCATION) {
goto error;
}
nret = EXIT_SUCCESS;
error:
return nret;
}
SIXELAPI int
sixel_allocator_tests_main(void)
{
int nret = EXIT_FAILURE;
size_t i;
typedef int (* testcase)(void);
static testcase const testcases[] = {
test1,
test2
};
for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
nret = testcases[i]();
if (nret != EXIT_SUCCESS) {
goto error;
}
}
nret = EXIT_SUCCESS;
error:
return nret;
}
#endif /* HAVE_TESTS */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2014-2016 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBSIXEL_ALLOCATOR_H
#define LIBSIXEL_ALLOCATOR_H
#include <sixel.h>
struct sixel_allocator {
unsigned int ref; /* reference counter */
sixel_malloc_t fn_malloc;
sixel_calloc_t fn_calloc;
sixel_realloc_t fn_realloc;
sixel_free_t fn_free;
};
#if HAVE_TESTS
int
sixel_allocator_tests_main(void);
#endif
#endif /* LIBSIXEL_ALLOCATOR_H */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+58
View File
@@ -0,0 +1,58 @@
/*
* Minimal hand-written replacement for the autoconf-generated config.h.
*
* Upstream libsixel ships config.h.in and lets `configure` produce config.h.
* We only vendor the encoder-side subset (see README.md), and those files
* reference a small, fixed set of macros, so the values are hard-coded here
* instead of carrying the autotools machinery.
*
* The values below hold for every platform fastfetch targets:
* Windows/MinGW-w64, Linux/glibc, Linux/musl, macOS and the BSDs.
* Re-check this file whenever the vendored subset is re-synced with upstream.
*/
#ifndef LIBSIXEL_CONFIG_H
#define LIBSIXEL_CONFIG_H
/* Standard headers (upstream: AC_CHECK_HEADERS) */
#define HAVE_ASSERT_H 1
#define HAVE_ERRNO_H 1
#define HAVE_INTTYPES_H 1
#define HAVE_LIMITS_H 1
#define HAVE_MATH_H 1
#define HAVE_MEMORY_H 1
#define HAVE_STRING_H 1
#define HAVE_SYS_TYPES_H 1
/* libc functions (upstream: AC_CHECK_FUNCS) */
#define HAVE_LDIV 1
#define HAVE_MALLOC 1
#define HAVE_REALLOC 1
/* Byte order of the 32bpp pixel readers in pixelformat.c; 0 = little endian */
#define SWAP_BYTES 0
/*
* Guards the diagnostic pragma push/pop in dither.c. Not needed, and the
* pragma spelling is not portable across the compilers we support.
*/
#define HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS 0
/* Verbose quantizer tracing to stderr */
#define HAVE_DEBUG 0
/*
* HAVE_TESTS must stay *undefined* rather than 0: sixel.h gates its test-only
* declarations with `#ifdef HAVE_TESTS`, so defining it to 0 would still pull
* in declarations for functions we do not compile.
*/
#undef HAVE_TESTS
/* Optional image loaders and features; not vendored */
#undef HAVE_GD
#undef HAVE_GDK_PIXBUF2
#undef HAVE_JPEG
#undef HAVE_LIBCURL
#undef HAVE_LIBPNG
#endif /* LIBSIXEL_CONFIG_H */
+918
View File
@@ -0,0 +1,918 @@
/*
* Copyright (c) 2014-2018 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#if HAVE_MATH_H
# include <math.h>
#endif /* HAVE_MATH_H */
#if HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#if HAVE_LIMITS_H
# include <limits.h>
#endif /* HAVE_LIMITS_H */
#if HAVE_INTTYPES_H
# include <inttypes.h>
#endif /* HAVE_INTTYPES_H */
#include "dither.h"
#include "quant.h"
#include <sixel.h>
static const unsigned char pal_mono_dark[] = {
0x00, 0x00, 0x00, 0xff, 0xff, 0xff
};
static const unsigned char pal_mono_light[] = {
0xff, 0xff, 0xff, 0x00, 0x00, 0x00
};
static const unsigned char pal_gray_1bit[] = {
0x00, 0x00, 0x00, 0xff, 0xff, 0xff
};
static const unsigned char pal_gray_2bit[] = {
0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff
};
static const unsigned char pal_gray_4bit[] = {
0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33,
0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77,
0x88, 0x88, 0x88, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb,
0xcc, 0xcc, 0xcc, 0xdd, 0xdd, 0xdd, 0xee, 0xee, 0xee, 0xff, 0xff, 0xff
};
static const unsigned char pal_gray_8bit[] = {
0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07,
0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b,
0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f,
0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13,
0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17,
0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b,
0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f,
0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23,
0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27,
0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b,
0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f,
0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33,
0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37,
0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b,
0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f,
0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43,
0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x47, 0x47, 0x47,
0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b,
0x4c, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f,
0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53,
0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57,
0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b,
0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f,
0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63,
0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67,
0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b,
0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f,
0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73,
0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77,
0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b,
0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f,
0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x83, 0x83, 0x83,
0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x86, 0x87, 0x87, 0x87,
0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b,
0x8c, 0x8c, 0x8c, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f,
0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93,
0x94, 0x94, 0x94, 0x95, 0x95, 0x95, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b,
0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f, 0x9f,
0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3,
0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa7, 0xa7, 0xa7,
0xa8, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab, 0xab,
0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf,
0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3,
0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7,
0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbb,
0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3,
0xc4, 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7,
0xc8, 0xc8, 0xc8, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb,
0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf,
0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3,
0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7,
0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb,
0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf,
0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3,
0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7,
0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xeb, 0xeb, 0xeb,
0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3,
0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7,
0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb,
0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff
};
static const unsigned char pal_xterm256[] = {
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0,
0x80, 0x80, 0x80, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x87, 0x00, 0x00, 0xaf,
0x00, 0x00, 0xd7, 0x00, 0x00, 0xff, 0x00, 0x5f, 0x00, 0x00, 0x5f, 0x5f,
0x00, 0x5f, 0x87, 0x00, 0x5f, 0xaf, 0x00, 0x5f, 0xd7, 0x00, 0x5f, 0xff,
0x00, 0x87, 0x00, 0x00, 0x87, 0x5f, 0x00, 0x87, 0x87, 0x00, 0x87, 0xaf,
0x00, 0x87, 0xd7, 0x00, 0x87, 0xff, 0x00, 0xaf, 0x00, 0x00, 0xaf, 0x5f,
0x00, 0xaf, 0x87, 0x00, 0xaf, 0xaf, 0x00, 0xaf, 0xd7, 0x00, 0xaf, 0xff,
0x00, 0xd7, 0x00, 0x00, 0xd7, 0x5f, 0x00, 0xd7, 0x87, 0x00, 0xd7, 0xaf,
0x00, 0xd7, 0xd7, 0x00, 0xd7, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0x5f,
0x00, 0xff, 0x87, 0x00, 0xff, 0xaf, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff,
0x5f, 0x00, 0x00, 0x5f, 0x00, 0x5f, 0x5f, 0x00, 0x87, 0x5f, 0x00, 0xaf,
0x5f, 0x00, 0xd7, 0x5f, 0x00, 0xff, 0x5f, 0x5f, 0x00, 0x5f, 0x5f, 0x5f,
0x5f, 0x5f, 0x87, 0x5f, 0x5f, 0xaf, 0x5f, 0x5f, 0xd7, 0x5f, 0x5f, 0xff,
0x5f, 0x87, 0x00, 0x5f, 0x87, 0x5f, 0x5f, 0x87, 0x87, 0x5f, 0x87, 0xaf,
0x5f, 0x87, 0xd7, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0x00, 0x5f, 0xaf, 0x5f,
0x5f, 0xaf, 0x87, 0x5f, 0xaf, 0xaf, 0x5f, 0xaf, 0xd7, 0x5f, 0xaf, 0xff,
0x5f, 0xd7, 0x00, 0x5f, 0xd7, 0x5f, 0x5f, 0xd7, 0x87, 0x5f, 0xd7, 0xaf,
0x5f, 0xd7, 0xd7, 0x5f, 0xd7, 0xff, 0x5f, 0xff, 0x00, 0x5f, 0xff, 0x5f,
0x5f, 0xff, 0x87, 0x5f, 0xff, 0xaf, 0x5f, 0xff, 0xd7, 0x5f, 0xff, 0xff,
0x87, 0x00, 0x00, 0x87, 0x00, 0x5f, 0x87, 0x00, 0x87, 0x87, 0x00, 0xaf,
0x87, 0x00, 0xd7, 0x87, 0x00, 0xff, 0x87, 0x5f, 0x00, 0x87, 0x5f, 0x5f,
0x87, 0x5f, 0x87, 0x87, 0x5f, 0xaf, 0x87, 0x5f, 0xd7, 0x87, 0x5f, 0xff,
0x87, 0x87, 0x00, 0x87, 0x87, 0x5f, 0x87, 0x87, 0x87, 0x87, 0x87, 0xaf,
0x87, 0x87, 0xd7, 0x87, 0x87, 0xff, 0x87, 0xaf, 0x00, 0x87, 0xaf, 0x5f,
0x87, 0xaf, 0x87, 0x87, 0xaf, 0xaf, 0x87, 0xaf, 0xd7, 0x87, 0xaf, 0xff,
0x87, 0xd7, 0x00, 0x87, 0xd7, 0x5f, 0x87, 0xd7, 0x87, 0x87, 0xd7, 0xaf,
0x87, 0xd7, 0xd7, 0x87, 0xd7, 0xff, 0x87, 0xff, 0x00, 0x87, 0xff, 0x5f,
0x87, 0xff, 0x87, 0x87, 0xff, 0xaf, 0x87, 0xff, 0xd7, 0x87, 0xff, 0xff,
0xaf, 0x00, 0x00, 0xaf, 0x00, 0x5f, 0xaf, 0x00, 0x87, 0xaf, 0x00, 0xaf,
0xaf, 0x00, 0xd7, 0xaf, 0x00, 0xff, 0xaf, 0x5f, 0x00, 0xaf, 0x5f, 0x5f,
0xaf, 0x5f, 0x87, 0xaf, 0x5f, 0xaf, 0xaf, 0x5f, 0xd7, 0xaf, 0x5f, 0xff,
0xaf, 0x87, 0x00, 0xaf, 0x87, 0x5f, 0xaf, 0x87, 0x87, 0xaf, 0x87, 0xaf,
0xaf, 0x87, 0xd7, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0x00, 0xaf, 0xaf, 0x5f,
0xaf, 0xaf, 0x87, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xd7, 0xaf, 0xaf, 0xff,
0xaf, 0xd7, 0x00, 0xaf, 0xd7, 0x5f, 0xaf, 0xd7, 0x87, 0xaf, 0xd7, 0xaf,
0xaf, 0xd7, 0xd7, 0xaf, 0xd7, 0xff, 0xaf, 0xff, 0x00, 0xaf, 0xff, 0x5f,
0xaf, 0xff, 0x87, 0xaf, 0xff, 0xaf, 0xaf, 0xff, 0xd7, 0xaf, 0xff, 0xff,
0xd7, 0x00, 0x00, 0xd7, 0x00, 0x5f, 0xd7, 0x00, 0x87, 0xd7, 0x00, 0xaf,
0xd7, 0x00, 0xd7, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0x00, 0xd7, 0x5f, 0x5f,
0xd7, 0x5f, 0x87, 0xd7, 0x5f, 0xaf, 0xd7, 0x5f, 0xd7, 0xd7, 0x5f, 0xff,
0xd7, 0x87, 0x00, 0xd7, 0x87, 0x5f, 0xd7, 0x87, 0x87, 0xd7, 0x87, 0xaf,
0xd7, 0x87, 0xd7, 0xd7, 0x87, 0xff, 0xd7, 0xaf, 0x00, 0xd7, 0xaf, 0x5f,
0xd7, 0xaf, 0x87, 0xd7, 0xaf, 0xaf, 0xd7, 0xaf, 0xd7, 0xd7, 0xaf, 0xff,
0xd7, 0xd7, 0x00, 0xd7, 0xd7, 0x5f, 0xd7, 0xd7, 0x87, 0xd7, 0xd7, 0xaf,
0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0x00, 0xd7, 0xff, 0x5f,
0xd7, 0xff, 0x87, 0xd7, 0xff, 0xaf, 0xd7, 0xff, 0xd7, 0xd7, 0xff, 0xff,
0xff, 0x00, 0x00, 0xff, 0x00, 0x5f, 0xff, 0x00, 0x87, 0xff, 0x00, 0xaf,
0xff, 0x00, 0xd7, 0xff, 0x00, 0xff, 0xff, 0x5f, 0x00, 0xff, 0x5f, 0x5f,
0xff, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0xff, 0x5f, 0xd7, 0xff, 0x5f, 0xff,
0xff, 0x87, 0x00, 0xff, 0x87, 0x5f, 0xff, 0x87, 0x87, 0xff, 0x87, 0xaf,
0xff, 0x87, 0xd7, 0xff, 0x87, 0xff, 0xff, 0xaf, 0x00, 0xff, 0xaf, 0x5f,
0xff, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0xff, 0xaf, 0xd7, 0xff, 0xaf, 0xff,
0xff, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0xff, 0xd7, 0x87, 0xff, 0xd7, 0xaf,
0xff, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x5f,
0xff, 0xff, 0x87, 0xff, 0xff, 0xaf, 0xff, 0xff, 0xd7, 0xff, 0xff, 0xff,
0x08, 0x08, 0x08, 0x12, 0x12, 0x12, 0x1c, 0x1c, 0x1c, 0x26, 0x26, 0x26,
0x30, 0x30, 0x30, 0x3a, 0x3a, 0x3a, 0x44, 0x44, 0x44, 0x4e, 0x4e, 0x4e,
0x58, 0x58, 0x58, 0x62, 0x62, 0x62, 0x6c, 0x6c, 0x6c, 0x76, 0x76, 0x76,
0x80, 0x80, 0x80, 0x8a, 0x8a, 0x8a, 0x94, 0x94, 0x94, 0x9e, 0x9e, 0x9e,
0xa8, 0xa8, 0xa8, 0xb2, 0xb2, 0xb2, 0xbc, 0xbc, 0xbc, 0xc6, 0xc6, 0xc6,
0xd0, 0xd0, 0xd0, 0xda, 0xda, 0xda, 0xe4, 0xe4, 0xe4, 0xee, 0xee, 0xee,
};
/*
* VT340 undocumented behavior regarding the color palette reported
* by Vertis Sidus(@vrtsds):
* it loads the first fifteen colors as 1 through 15, and loads the
* sixteenth color as 0.
*/
static const unsigned char pal_vt340_mono[] = {
/* 1 Gray-2 */ 13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
/* 2 Gray-4 */ 26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
/* 3 Gray-6 */ 40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
/* 4 Gray-1 */ 6 * 255 / 100, 6 * 255 / 100, 6 * 255 / 100,
/* 5 Gray-3 */ 20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
/* 6 Gray-5 */ 33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
/* 7 White 7 */ 46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
/* 8 Black 0 */ 0 * 255 / 100, 0 * 255 / 100, 0 * 255 / 100,
/* 9 Gray-2 */ 13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
/* 10 Gray-4 */ 26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
/* 11 Gray-6 */ 40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
/* 12 Gray-1 */ 6 * 255 / 100, 6 * 255 / 100, 6 * 255 / 100,
/* 13 Gray-3 */ 20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
/* 14 Gray-5 */ 33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
/* 15 White 7 */ 46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
/* 0 Black */ 0 * 255 / 100, 0 * 255 / 100, 0 * 255 / 100,
};
static const unsigned char pal_vt340_color[] = {
/* 1 Blue */ 20 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
/* 2 Red */ 80 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
/* 3 Green */ 20 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
/* 4 Magenta */ 80 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
/* 5 Cyan */ 20 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
/* 6 Yellow */ 80 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
/* 7 Gray 50% */ 53 * 255 / 100, 53 * 255 / 100, 53 * 255 / 100,
/* 8 Gray 25% */ 26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
/* 9 Blue* */ 33 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
/* 10 Red* */ 60 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
/* 11 Green* */ 33 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
/* 12 Magenta* */ 60 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
/* 13 Cyan* */ 33 * 255 / 100, 60 * 255 / 100, 60 * 255 / 100,
/* 14 Yellow* */ 60 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
/* 15 Gray 75% */ 80 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
/* 0 Black */ 0 * 255 / 100, 0 * 255 / 100, 0 * 255 / 100,
};
/* create dither context object */
SIXELAPI SIXELSTATUS
sixel_dither_new(
sixel_dither_t /* out */ **ppdither, /* dither object to be created */
int /* in */ ncolors, /* required colors */
sixel_allocator_t /* in */ *allocator) /* allocator, null if you use
default allocator */
{
SIXELSTATUS status = SIXEL_FALSE;
size_t headsize;
size_t datasize;
size_t wholesize;
int quality_mode;
/* ensure given pointer is not null */
if (ppdither == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_new: ppdither is null.");
status = SIXEL_BAD_ARGUMENT;
goto end;
}
if (allocator == NULL) {
status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
if (SIXEL_FAILED(status)) {
*ppdither = NULL;
goto end;
}
} else {
sixel_allocator_ref(allocator);
}
if (ncolors < 0) {
ncolors = SIXEL_PALETTE_MAX;
quality_mode = SIXEL_QUALITY_HIGHCOLOR;
} else {
if (ncolors > SIXEL_PALETTE_MAX) {
status = SIXEL_BAD_INPUT;
goto end;
} else if (ncolors < 1) {
status = SIXEL_BAD_INPUT;
sixel_helper_set_additional_message(
"sixel_dither_new: palette colors must be more than 0");
goto end;
}
quality_mode = SIXEL_QUALITY_LOW;
}
headsize = sizeof(sixel_dither_t);
datasize = (size_t)(ncolors * 3);
wholesize = headsize + datasize;
*ppdither = (sixel_dither_t *)sixel_allocator_malloc(allocator, wholesize);
if (*ppdither == NULL) {
sixel_allocator_unref(allocator);
sixel_helper_set_additional_message(
"sixel_dither_new: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
(*ppdither)->ref = 1;
(*ppdither)->palette = (unsigned char*)(*ppdither + 1);
(*ppdither)->cachetable = NULL;
(*ppdither)->reqcolors = ncolors;
(*ppdither)->ncolors = ncolors;
(*ppdither)->origcolors = (-1);
(*ppdither)->keycolor = (-1);
(*ppdither)->optimized = 0;
(*ppdither)->optimize_palette = 0;
(*ppdither)->complexion = 1;
(*ppdither)->bodyonly = 0;
(*ppdither)->method_for_largest = SIXEL_LARGE_NORM;
(*ppdither)->method_for_rep = SIXEL_REP_CENTER_BOX;
(*ppdither)->method_for_diffuse = SIXEL_DIFFUSE_FS;
(*ppdither)->quality_mode = quality_mode;
(*ppdither)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
(*ppdither)->allocator = allocator;
status = SIXEL_OK;
end:
return status;
}
/* create dither context object (deprecated) */
SIXELAPI sixel_dither_t *
sixel_dither_create(
int /* in */ ncolors)
{
SIXELSTATUS status = SIXEL_FALSE;
sixel_dither_t *dither = NULL;
status = sixel_dither_new(&dither, ncolors, NULL);
if (SIXEL_FAILED(status)) {
goto end;
}
end:
return dither;
}
SIXELAPI void
sixel_dither_destroy(
sixel_dither_t /* in */ *dither)
{
sixel_allocator_t *allocator;
if (dither) {
allocator = dither->allocator;
sixel_allocator_free(allocator, dither->cachetable);
dither->cachetable = NULL;
sixel_allocator_free(allocator, dither);
sixel_allocator_unref(allocator);
}
}
SIXELAPI void
sixel_dither_ref(
sixel_dither_t /* in */ *dither)
{
/* TODO: be thread safe */
++dither->ref;
}
SIXELAPI void
sixel_dither_unref(
sixel_dither_t /* in */ *dither)
{
/* TODO: be thread safe */
if (dither != NULL && --dither->ref == 0) {
sixel_dither_destroy(dither);
}
}
SIXELAPI sixel_dither_t *
sixel_dither_get(
int /* in */ builtin_dither)
{
SIXELSTATUS status = SIXEL_FALSE;
unsigned char *palette;
int ncolors;
int keycolor;
sixel_dither_t *dither = NULL;
switch (builtin_dither) {
case SIXEL_BUILTIN_MONO_DARK:
ncolors = 2;
palette = (unsigned char *)pal_mono_dark;
keycolor = 0;
break;
case SIXEL_BUILTIN_MONO_LIGHT:
ncolors = 2;
palette = (unsigned char *)pal_mono_light;
keycolor = 0;
break;
case SIXEL_BUILTIN_XTERM16:
ncolors = 16;
palette = (unsigned char *)pal_xterm256;
keycolor = (-1);
break;
case SIXEL_BUILTIN_XTERM256:
ncolors = 256;
palette = (unsigned char *)pal_xterm256;
keycolor = (-1);
break;
case SIXEL_BUILTIN_VT340_MONO:
ncolors = 16;
palette = (unsigned char *)pal_vt340_mono;
keycolor = (-1);
break;
case SIXEL_BUILTIN_VT340_COLOR:
ncolors = 16;
palette = (unsigned char *)pal_vt340_color;
keycolor = (-1);
break;
case SIXEL_BUILTIN_G1:
ncolors = 2;
palette = (unsigned char *)pal_gray_1bit;
keycolor = (-1);
break;
case SIXEL_BUILTIN_G2:
ncolors = 4;
palette = (unsigned char *)pal_gray_2bit;
keycolor = (-1);
break;
case SIXEL_BUILTIN_G4:
ncolors = 16;
palette = (unsigned char *)pal_gray_4bit;
keycolor = (-1);
break;
case SIXEL_BUILTIN_G8:
ncolors = 256;
palette = (unsigned char *)pal_gray_8bit;
keycolor = (-1);
break;
default:
goto end;
}
status = sixel_dither_new(&dither, ncolors, NULL);
if (SIXEL_FAILED(status)) {
dither = NULL;
goto end;
}
dither->palette = palette;
dither->keycolor = keycolor;
dither->optimized = 1;
dither->optimize_palette = 0;
end:
return dither;
}
static void
sixel_dither_set_method_for_largest(
sixel_dither_t /* in */ *dither,
int /* in */ method_for_largest)
{
if (method_for_largest == SIXEL_LARGE_AUTO) {
method_for_largest = SIXEL_LARGE_NORM;
}
dither->method_for_largest = method_for_largest;
}
static void
sixel_dither_set_method_for_rep(
sixel_dither_t /* in */ *dither,
int /* in */ method_for_rep)
{
if (method_for_rep == SIXEL_REP_AUTO) {
method_for_rep = SIXEL_REP_CENTER_BOX;
}
dither->method_for_rep = method_for_rep;
}
static void
sixel_dither_set_quality_mode(
sixel_dither_t /* in */ *dither,
int /* in */ quality_mode)
{
if (quality_mode == SIXEL_QUALITY_AUTO) {
if (dither->ncolors <= 8) {
quality_mode = SIXEL_QUALITY_HIGH;
} else {
quality_mode = SIXEL_QUALITY_LOW;
}
}
dither->quality_mode = quality_mode;
}
SIXELAPI SIXELSTATUS
sixel_dither_initialize(
sixel_dither_t /* in */ *dither,
unsigned char /* in */ *data,
int /* in */ width,
int /* in */ height,
int /* in */ pixelformat,
int /* in */ method_for_largest,
int /* in */ method_for_rep,
int /* in */ quality_mode)
{
unsigned char *buf = NULL;
unsigned char *normalized_pixels = NULL;
unsigned char *input_pixels;
SIXELSTATUS status = SIXEL_FALSE;
/* ensure dither object is not null */
if (dither == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_new: dither is null.");
status = SIXEL_BAD_ARGUMENT;
goto end;
}
/* increment ref count */
sixel_dither_ref(dither);
sixel_dither_set_pixelformat(dither, pixelformat);
switch (pixelformat) {
case SIXEL_PIXELFORMAT_RGB888:
input_pixels = data;
break;
default:
/* normalize pixelformat */
normalized_pixels
= (unsigned char *)sixel_allocator_malloc(dither->allocator, (size_t)(width * height * 3));
if (normalized_pixels == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_initialize: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
status = sixel_helper_normalize_pixelformat(
normalized_pixels,
&pixelformat,
data,
pixelformat,
width,
height);
if (SIXEL_FAILED(status)) {
goto end;
}
input_pixels = normalized_pixels;
break;
}
sixel_dither_set_method_for_largest(dither, method_for_largest);
sixel_dither_set_method_for_rep(dither, method_for_rep);
sixel_dither_set_quality_mode(dither, quality_mode);
status = sixel_quant_make_palette(&buf,
input_pixels,
(unsigned int)(width * height * 3),
SIXEL_PIXELFORMAT_RGB888,
(unsigned int)dither->reqcolors,
(unsigned int *)&dither->ncolors,
(unsigned int *)&dither->origcolors,
dither->method_for_largest,
dither->method_for_rep,
dither->quality_mode,
dither->allocator);
if (SIXEL_FAILED(status)) {
goto end;
}
memcpy(dither->palette, buf, (size_t)(dither->ncolors * 3));
dither->optimized = 1;
if (dither->origcolors <= dither->ncolors) {
dither->method_for_diffuse = SIXEL_DIFFUSE_NONE;
}
sixel_quant_free_palette(buf, dither->allocator);
status = SIXEL_OK;
end:
free(normalized_pixels);
/* decrement ref count */
sixel_dither_unref(dither);
return status;
}
/* set diffusion type, choose from enum methodForDiffuse */
SIXELAPI void
sixel_dither_set_diffusion_type(
sixel_dither_t /* in */ *dither,
int /* in */ method_for_diffuse)
{
if (method_for_diffuse == SIXEL_DIFFUSE_AUTO) {
if (dither->ncolors > 16) {
method_for_diffuse = SIXEL_DIFFUSE_FS;
} else {
method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
}
}
dither->method_for_diffuse = method_for_diffuse;
}
/* get number of palette colors */
SIXELAPI int
sixel_dither_get_num_of_palette_colors(
sixel_dither_t /* in */ *dither)
{
return dither->ncolors;
}
/* get number of histogram colors */
SIXELAPI int
sixel_dither_get_num_of_histogram_colors(
sixel_dither_t /* in */ *dither) /* dither context object */
{
return dither->origcolors;
}
/* typoed: remained for keeping compatibility */
SIXELAPI int
sixel_dither_get_num_of_histgram_colors(
sixel_dither_t /* in */ *dither) /* dither context object */
{
return sixel_dither_get_num_of_histogram_colors(dither);
}
/* get palette */
SIXELAPI unsigned char *
sixel_dither_get_palette(
sixel_dither_t /* in */ *dither) /* dither context object */
{
return dither->palette;
}
/* set palette */
SIXELAPI void
sixel_dither_set_palette(
sixel_dither_t /* in */ *dither, /* dither context object */
unsigned char /* in */ *palette)
{
memcpy(dither->palette, palette, (size_t)(dither->ncolors * 3));
}
/* set the factor of complexion color correcting */
SIXELAPI void
sixel_dither_set_complexion_score(
sixel_dither_t /* in */ *dither, /* dither context object */
int /* in */ score) /* complexion score (>= 1) */
{
dither->complexion = score;
}
/* set whether omitting palette difinition */
SIXELAPI void
sixel_dither_set_body_only(
sixel_dither_t /* in */ *dither, /* dither context object */
int /* in */ bodyonly) /* 0: output palette section
1: do not output palette section */
{
dither->bodyonly = bodyonly;
}
/* set whether optimize palette size */
SIXELAPI void
sixel_dither_set_optimize_palette(
sixel_dither_t /* in */ *dither, /* dither context object */
int /* in */ do_opt) /* 0: optimize palette size
1: don't optimize palette size */
{
dither->optimize_palette = do_opt;
}
/* set pixelformat */
SIXELAPI void
sixel_dither_set_pixelformat(
sixel_dither_t /* in */ *dither, /* dither context object */
int /* in */ pixelformat) /* one of enum pixelFormat */
{
dither->pixelformat = pixelformat;
}
/* set transparent */
SIXELAPI void
sixel_dither_set_transparent(
sixel_dither_t /* in */ *dither, /* dither context object */
int /* in */ transparent) /* transparent color index */
{
dither->keycolor = transparent;
}
/* set transparent */
SIXELAPI sixel_index_t *
sixel_dither_apply_palette(
sixel_dither_t /* in */ *dither,
unsigned char /* in */ *pixels,
int /* in */ width,
int /* in */ height)
{
SIXELSTATUS status = SIXEL_FALSE;
size_t bufsize;
sixel_index_t *dest = NULL;
int ncolors;
unsigned char *normalized_pixels = NULL;
unsigned char *input_pixels;
/* ensure dither object is not null */
if (dither == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_apply_palette: dither is null.");
status = SIXEL_BAD_ARGUMENT;
goto end;
}
sixel_dither_ref(dither);
bufsize = (size_t)(width * height) * sizeof(sixel_index_t);
dest = (sixel_index_t *)sixel_allocator_malloc(dither->allocator, bufsize);
if (dest == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_new: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
/* if quality_mode is full, do not use palette caching */
if (dither->quality_mode == SIXEL_QUALITY_FULL) {
dither->optimized = 0;
}
if (dither->cachetable == NULL && dither->optimized) {
if (dither->palette != pal_mono_dark && dither->palette != pal_mono_light) {
dither->cachetable = (unsigned short *)sixel_allocator_calloc(dither->allocator,
(size_t)(1 << 3 * 5),
sizeof(unsigned short));
if (dither->cachetable == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_new: sixel_allocator_calloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
}
}
if (dither->pixelformat != SIXEL_PIXELFORMAT_RGB888) {
/* normalize pixelformat */
normalized_pixels
= (unsigned char *)sixel_allocator_malloc(dither->allocator, (size_t)(width * height * 3));
if (normalized_pixels == NULL) {
sixel_helper_set_additional_message(
"sixel_dither_new: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
status = sixel_helper_normalize_pixelformat(normalized_pixels,
&dither->pixelformat,
pixels, dither->pixelformat,
width, height);
if (SIXEL_FAILED(status)) {
goto end;
}
input_pixels = normalized_pixels;
} else {
input_pixels = pixels;
}
status = sixel_quant_apply_palette(dest,
input_pixels,
width, height, 3,
dither->palette,
dither->ncolors,
dither->method_for_diffuse,
dither->optimized,
dither->optimize_palette,
dither->complexion,
dither->cachetable,
&ncolors,
dither->allocator);
if (SIXEL_FAILED(status)) {
free(dest);
dest = NULL;
goto end;
}
dither->ncolors = ncolors;
end:
free(normalized_pixels);
sixel_dither_unref(dither);
return dest;
}
#if HAVE_TESTS
static int
test1(void)
{
sixel_dither_t *dither = NULL;
int nret = EXIT_FAILURE;
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
dither = sixel_dither_create(2);
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
# pragma GCC diagnostic pop
#endif
if (dither == NULL) {
goto error;
}
sixel_dither_ref(dither);
sixel_dither_unref(dither);
nret = EXIT_SUCCESS;
error:
sixel_dither_unref(dither);
return nret;
}
static int
test2(void)
{
sixel_dither_t *dither = NULL;
int colors;
int nret = EXIT_FAILURE;
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
dither = sixel_dither_create(INT_MAX);
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
# pragma GCC diagnostic pop
#endif
if (dither == NULL) {
goto error;
}
sixel_dither_set_body_only(dither, 1);
colors = sixel_dither_get_num_of_histogram_colors(dither);
if (colors != -1) {
goto error;
}
nret = EXIT_SUCCESS;
error:
sixel_dither_unref(dither);
return nret;
}
SIXELAPI int
sixel_dither_tests_main(void)
{
int nret = EXIT_FAILURE;
size_t i;
typedef int (* testcase)(void);
static testcase const testcases[] = {
test1,
test2,
};
for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
nret = testcases[i]();
if (nret != EXIT_SUCCESS) {
goto error;
}
}
nret = EXIT_SUCCESS;
error:
return nret;
}
#endif /* HAVE_TESTS */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2014-2016 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBSIXEL_DITHER_H
#define LIBSIXEL_DITHER_H
#include <sixel.h>
/* dither context object */
struct sixel_dither {
unsigned int ref; /* reference counter */
unsigned char *palette; /* palette definition */
unsigned short *cachetable; /* cache table */
int reqcolors; /* requested colors */
int ncolors; /* active colors */
int origcolors; /* original colors */
int optimized; /* pixel is 15bpp compressable */
int optimize_palette; /* minimize palette size */
int complexion; /* for complexion correction */
int bodyonly; /* do not output palette section if true */
int method_for_largest; /* method for finding the largest dimention
for splitting */
int method_for_rep; /* method for choosing a color from the box */
int method_for_diffuse; /* method for diffusing */
int quality_mode; /* quality of histogram */
int keycolor; /* background color */
int pixelformat; /* pixelformat for internal processing */
sixel_allocator_t *allocator; /* allocator */
};
#ifdef __cplusplus
extern "C" {
#endif
/* apply palette */
sixel_index_t *
sixel_dither_apply_palette(struct sixel_dither /* in */ *dither,
unsigned char /* in */ *pixels,
int /* in */ width,
int /* in */ height);
#if HAVE_TESTS
int
sixel_frame_tests_main(void);
#endif
#ifdef __cplusplus
}
#endif
#endif /* LIBSIXEL_DITHER_H */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2014-2016 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MALLOC_STUB_H
#define MALLOC_STUB_H
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#if !HAVE_MALLOC
void * rpl_malloc(size_t n);
#endif /* !HAVE_MALLOC */
#if !HAVE_REALLOC
void * rpl_realloc(void *p, size_t n);
#endif /* !HAVE_REALLOC */
#if 0
int rpl_posix_memalign(void **memptr, size_t alignment, size_t size);
#endif
#endif /* MALLOC_STUB_H */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+211
View File
@@ -0,0 +1,211 @@
/*
* Copyright (c) 2014-2019 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
/* STDC_HEADERS */
# include <stdio.h>
# include <stdlib.h>
#if HAVE_ASSERT_H
# include <assert.h>
#endif /* HAVE_ASSERT_H */
#include <sixel.h>
#include "output.h"
/* create new output context object */
SIXELAPI SIXELSTATUS
sixel_output_new(
sixel_output_t /* out */ **output,
sixel_write_function /* in */ fn_write,
void /* in */ *priv,
sixel_allocator_t /* in */ *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
size_t size;
if (allocator == NULL) {
status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
if (SIXEL_FAILED(status)) {
goto end;
}
} else {
sixel_allocator_ref(allocator);
}
size = sizeof(sixel_output_t) + SIXEL_OUTPUT_PACKET_SIZE * 2;
*output = (sixel_output_t *)sixel_allocator_malloc(allocator, size);
if (*output == NULL) {
sixel_helper_set_additional_message(
"sixel_output_new: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
sixel_allocator_unref(allocator);
goto end;
}
(*output)->ref = 1;
(*output)->has_8bit_control = 0;
(*output)->has_sdm_glitch = 0;
(*output)->has_gri_arg_limit = 1;
(*output)->skip_dcs_envelope = 0;
(*output)->palette_type = SIXEL_PALETTETYPE_AUTO;
(*output)->fn_write = fn_write;
(*output)->save_pixel = 0;
(*output)->save_count = 0;
(*output)->active_palette = (-1);
(*output)->node_top = NULL;
(*output)->node_free = NULL;
(*output)->priv = priv;
(*output)->pos = 0;
(*output)->penetrate_multiplexer = 0;
(*output)->encode_policy = SIXEL_ENCODEPOLICY_AUTO;
(*output)->allocator = allocator;
status = SIXEL_OK;
end:
return status;
}
/* deprecated: create an output object */
SIXELAPI sixel_output_t *
sixel_output_create(sixel_write_function fn_write, void *priv)
{
SIXELSTATUS status = SIXEL_FALSE;
sixel_output_t *output = NULL;
status = sixel_output_new(&output, fn_write, priv, NULL);
if (SIXEL_FAILED(status)) {
goto end;
}
end:
return output;
}
/* destroy output context object */
SIXELAPI void
sixel_output_destroy(sixel_output_t *output)
{
sixel_allocator_t *allocator;
if (output) {
allocator = output->allocator;
sixel_allocator_free(allocator, output);
sixel_allocator_unref(allocator);
}
}
/* increase reference count of output context object (thread-unsafe) */
SIXELAPI void
sixel_output_ref(sixel_output_t *output)
{
/* TODO: be thread-safe */
++output->ref;
}
/* decrease reference count of output context object (thread-unsafe) */
SIXELAPI void
sixel_output_unref(sixel_output_t *output)
{
/* TODO: be thread-safe */
if (output) {
assert(output->ref > 0);
output->ref--;
if (output->ref == 0) {
sixel_output_destroy(output);
}
}
}
/* get 8bit output mode which indicates whether it uses C1 control characters */
SIXELAPI int
sixel_output_get_8bit_availability(sixel_output_t *output)
{
return output->has_8bit_control;
}
/* set 8bit output mode state */
SIXELAPI void
sixel_output_set_8bit_availability(sixel_output_t *output, int availability)
{
output->has_8bit_control = availability;
}
/* set whether limit arguments of DECGRI('!') to 255 */
SIXELAPI void
sixel_output_set_gri_arg_limit(
sixel_output_t /* in */ *output, /* output context */
int /* in */ value) /* 0: don't limit arguments of DECGRI
1: limit arguments of DECGRI to 255 */
{
output->has_gri_arg_limit = value;
}
/* set GNU Screen penetration feature enable or disable */
SIXELAPI void
sixel_output_set_penetrate_multiplexer(sixel_output_t *output, int penetrate)
{
output->penetrate_multiplexer = penetrate;
}
/* set whether we skip DCS envelope */
SIXELAPI void
sixel_output_set_skip_dcs_envelope(sixel_output_t *output, int skip)
{
output->skip_dcs_envelope = skip;
}
/* set palette type: RGB or HLS */
SIXELAPI void
sixel_output_set_palette_type(sixel_output_t *output, int palettetype)
{
output->palette_type = palettetype;
}
/* set encodeing policy: auto, fast or size */
SIXELAPI void
sixel_output_set_encode_policy(sixel_output_t *output, int encode_policy)
{
output->encode_policy = encode_policy;
}
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+91
View File
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2014-2016 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBSIXEL_OUTPUT_H
#define LIBSIXEL_OUTPUT_H
typedef struct sixel_node {
struct sixel_node *next;
int pal;
int sx;
int mx;
char *map;
} sixel_node_t;
struct sixel_output {
int ref;
sixel_allocator_t *allocator;
/* compatiblity flags */
/* 0: 7bit terminal,
* 1: 8bit terminal */
unsigned char has_8bit_control;
/* 0: the terminal has sixel scrolling
* 1: the terminal does not have sixel scrolling */
unsigned char has_sixel_scrolling;
/* 1: the argument of repeat introducer(DECGRI) is not limitted
0: the argument of repeat introducer(DECGRI) is limitted 255 */
unsigned char has_gri_arg_limit;
/* 0: DECSDM set (CSI ? 80 h) enables sixel scrolling
1: DECSDM set (CSI ? 80 h) disables sixel scrolling */
unsigned char has_sdm_glitch;
/* 0: do not skip DCS envelope
* 1: skip DCS envelope */
unsigned char skip_dcs_envelope;
/* PALETTETYPE_AUTO: select palette type automatically
* PALETTETYPE_HLS : HLS color space
* PALETTETYPE_RGB : RGB color space */
unsigned char palette_type;
sixel_write_function fn_write;
int save_pixel;
int save_count;
int active_palette;
sixel_node_t *node_top;
sixel_node_t *node_free;
int penetrate_multiplexer;
int encode_policy;
void *priv;
int pos;
unsigned char buffer[1];
};
#endif /* LIBSIXEL_OUTPUT_H */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+725
View File
@@ -0,0 +1,725 @@
/*
* Copyright (c) 2014-2019 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
/* STDC_HEADERS */
#include <stdio.h>
#include <stdlib.h>
#if HAVE_MEMORY_H
# include <memory.h>
#endif /* HAVE_MEMORY_H */
#include <sixel.h>
static void
get_rgb(unsigned char const *data,
int const pixelformat,
int depth,
unsigned char *r,
unsigned char *g,
unsigned char *b)
{
unsigned int pixels = 0;
#if SWAP_BYTES
unsigned int low;
unsigned int high;
#endif
int count = 0;
while (count < depth) {
pixels = *(data + count) | (pixels << 8);
count++;
}
/* TODO: we should swap bytes (only necessary on LSByte first hardware?) */
#if SWAP_BYTES
if (depth == 2) {
low = pixels & 0xff;
high = (pixels >> 8) & 0xff;
pixels = (low << 8) | high;
}
#endif
switch (pixelformat) {
case SIXEL_PIXELFORMAT_RGB555:
*r = ((pixels >> 10) & 0x1f) << 3;
*g = ((pixels >> 5) & 0x1f) << 3;
*b = ((pixels >> 0) & 0x1f) << 3;
break;
case SIXEL_PIXELFORMAT_RGB565:
*r = ((pixels >> 11) & 0x1f) << 3;
*g = ((pixels >> 5) & 0x3f) << 2;
*b = ((pixels >> 0) & 0x1f) << 3;
break;
case SIXEL_PIXELFORMAT_RGB888:
*r = (pixels >> 16) & 0xff;
*g = (pixels >> 8) & 0xff;
*b = (pixels >> 0) & 0xff;
break;
case SIXEL_PIXELFORMAT_BGR555:
*r = ((pixels >> 0) & 0x1f) << 3;
*g = ((pixels >> 5) & 0x1f) << 3;
*b = ((pixels >> 10) & 0x1f) << 3;
break;
case SIXEL_PIXELFORMAT_BGR565:
*r = ((pixels >> 0) & 0x1f) << 3;
*g = ((pixels >> 5) & 0x3f) << 2;
*b = ((pixels >> 11) & 0x1f) << 3;
break;
case SIXEL_PIXELFORMAT_BGR888:
*r = (pixels >> 0) & 0xff;
*g = (pixels >> 8) & 0xff;
*b = (pixels >> 16) & 0xff;
break;
case SIXEL_PIXELFORMAT_RGBA8888:
*r = (pixels >> 24) & 0xff;
*g = (pixels >> 16) & 0xff;
*b = (pixels >> 8) & 0xff;
break;
case SIXEL_PIXELFORMAT_ARGB8888:
*r = (pixels >> 16) & 0xff;
*g = (pixels >> 8) & 0xff;
*b = (pixels >> 0) & 0xff;
break;
case SIXEL_PIXELFORMAT_BGRA8888:
*r = (pixels >> 8) & 0xff;
*g = (pixels >> 16) & 0xff;
*b = (pixels >> 24) & 0xff;
break;
case SIXEL_PIXELFORMAT_ABGR8888:
*r = (pixels >> 0) & 0xff;
*g = (pixels >> 8) & 0xff;
*b = (pixels >> 16) & 0xff;
break;
case SIXEL_PIXELFORMAT_GA88:
*r = *g = *b = (pixels >> 8) & 0xff;
break;
case SIXEL_PIXELFORMAT_G8:
case SIXEL_PIXELFORMAT_AG88:
*r = *g = *b = pixels & 0xff;
break;
default:
*r = *g = *b = 0;
break;
}
}
SIXELAPI int
sixel_helper_compute_depth(int pixelformat)
{
int depth = (-1); /* unknown */
switch (pixelformat) {
case SIXEL_PIXELFORMAT_ARGB8888:
case SIXEL_PIXELFORMAT_RGBA8888:
case SIXEL_PIXELFORMAT_ABGR8888:
case SIXEL_PIXELFORMAT_BGRA8888:
depth = 4;
break;
case SIXEL_PIXELFORMAT_RGB888:
case SIXEL_PIXELFORMAT_BGR888:
depth = 3;
break;
case SIXEL_PIXELFORMAT_RGB555:
case SIXEL_PIXELFORMAT_RGB565:
case SIXEL_PIXELFORMAT_BGR555:
case SIXEL_PIXELFORMAT_BGR565:
case SIXEL_PIXELFORMAT_AG88:
case SIXEL_PIXELFORMAT_GA88:
depth = 2;
break;
case SIXEL_PIXELFORMAT_G1:
case SIXEL_PIXELFORMAT_G2:
case SIXEL_PIXELFORMAT_G4:
case SIXEL_PIXELFORMAT_G8:
case SIXEL_PIXELFORMAT_PAL1:
case SIXEL_PIXELFORMAT_PAL2:
case SIXEL_PIXELFORMAT_PAL4:
case SIXEL_PIXELFORMAT_PAL8:
depth = 1;
break;
default:
break;
}
return depth;
}
static void
expand_rgb(unsigned char *dst,
unsigned char const *src,
int width, int height,
int pixelformat, int depth)
{
int x;
int y;
int dst_offset;
int src_offset;
unsigned char r, g, b;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
src_offset = depth * (y * width + x);
dst_offset = 3 * (y * width + x);
get_rgb(src + src_offset, pixelformat, depth, &r, &g, &b);
*(dst + dst_offset + 0) = r;
*(dst + dst_offset + 1) = g;
*(dst + dst_offset + 2) = b;
}
}
}
static SIXELSTATUS
expand_palette(unsigned char *dst, unsigned char const *src,
int width, int height, int const pixelformat)
{
SIXELSTATUS status = SIXEL_FALSE;
int x;
int y;
int i;
int bpp; /* bit per plane */
switch (pixelformat) {
case SIXEL_PIXELFORMAT_PAL1:
case SIXEL_PIXELFORMAT_G1:
bpp = 1;
break;
case SIXEL_PIXELFORMAT_PAL2:
case SIXEL_PIXELFORMAT_G2:
bpp = 2;
break;
case SIXEL_PIXELFORMAT_PAL4:
case SIXEL_PIXELFORMAT_G4:
bpp = 4;
break;
case SIXEL_PIXELFORMAT_PAL8:
case SIXEL_PIXELFORMAT_G8:
for (i = 0; i < width * height; ++i, ++src) {
*dst++ = *src;
}
status = SIXEL_OK;
goto end;
default:
status = SIXEL_BAD_ARGUMENT;
sixel_helper_set_additional_message(
"expand_palette: invalid pixelformat.");
goto end;
}
#if HAVE_DEBUG
fprintf(stderr, "expanding PAL%d to PAL8...\n", bpp);
#endif
for (y = 0; y < height; ++y) {
for (x = 0; x < width * bpp / 8; ++x) {
for (i = 0; i < 8 / bpp; ++i) {
*dst++ = *src >> (8 / bpp - 1 - i) * bpp & ((1 << bpp) - 1);
}
src++;
}
x = width - x * 8 / bpp;
if (x > 0) {
for (i = 0; i < x; ++i) {
*dst++ = *src >> (8 - (i + 1) * bpp) & ((1 << bpp) - 1);
}
src++;
}
}
status = SIXEL_OK;
end:
return status;
}
SIXELAPI SIXELSTATUS
sixel_helper_normalize_pixelformat(
unsigned char /* out */ *dst, /* destination buffer */
int /* out */ *dst_pixelformat, /* converted pixelformat */
unsigned char const /* in */ *src, /* source pixels */
int /* in */ src_pixelformat, /* format of source image */
int /* in */ width, /* width of source image */
int /* in */ height) /* height of source image */
{
SIXELSTATUS status = SIXEL_FALSE;
switch (src_pixelformat) {
case SIXEL_PIXELFORMAT_G8:
expand_rgb(dst, src, width, height, src_pixelformat, 1);
*dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
break;
case SIXEL_PIXELFORMAT_RGB565:
case SIXEL_PIXELFORMAT_RGB555:
case SIXEL_PIXELFORMAT_BGR565:
case SIXEL_PIXELFORMAT_BGR555:
case SIXEL_PIXELFORMAT_GA88:
case SIXEL_PIXELFORMAT_AG88:
expand_rgb(dst, src, width, height, src_pixelformat, 2);
*dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
break;
case SIXEL_PIXELFORMAT_RGB888:
case SIXEL_PIXELFORMAT_BGR888:
expand_rgb(dst, src, width, height, src_pixelformat, 3);
*dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
break;
case SIXEL_PIXELFORMAT_RGBA8888:
case SIXEL_PIXELFORMAT_ARGB8888:
case SIXEL_PIXELFORMAT_BGRA8888:
case SIXEL_PIXELFORMAT_ABGR8888:
expand_rgb(dst, src, width, height, src_pixelformat, 4);
*dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
break;
case SIXEL_PIXELFORMAT_PAL1:
case SIXEL_PIXELFORMAT_PAL2:
case SIXEL_PIXELFORMAT_PAL4:
*dst_pixelformat = SIXEL_PIXELFORMAT_PAL8;
status = expand_palette(dst, src, width, height, src_pixelformat);
if (SIXEL_FAILED(status)) {
goto end;
}
break;
case SIXEL_PIXELFORMAT_G1:
case SIXEL_PIXELFORMAT_G2:
case SIXEL_PIXELFORMAT_G4:
*dst_pixelformat = SIXEL_PIXELFORMAT_G8;
status = expand_palette(dst, src, width, height, src_pixelformat);
if (SIXEL_FAILED(status)) {
goto end;
}
break;
case SIXEL_PIXELFORMAT_PAL8:
memcpy(dst, src, (size_t)(width * height));
*dst_pixelformat = src_pixelformat;
break;
default:
status = SIXEL_BAD_ARGUMENT;
goto end;
}
status = SIXEL_OK;
end:
return status;
}
#if HAVE_TESTS
static int
test1(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_RGB888;
unsigned char src[] = { 0x46, 0xf3, 0xe5 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if ((dst[0] << 16 | dst[1] << 8 | dst[2]) != (src[0] << 16 | src[1] << 8 | src[2])) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test1");
return nret;
}
static int
test2(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_RGB555;
unsigned char src[] = { 0x47, 0x9c };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if ((dst[0] >> 3 << 10 | dst[1] >> 3 << 5 | dst[2] >> 3) != (src[0] << 8 | src[1])) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test2");
return nret;
}
static int
test3(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_RGB565;
unsigned char src[] = { 0x47, 0x9c };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if ((dst[0] >> 3 << 11 | dst[1] >> 2 << 5 | dst[2] >> 3) != (src[0] << 8 | src[1])) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test3");
return nret;
}
static int
test4(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_BGR888;
unsigned char src[] = { 0x46, 0xf3, 0xe5 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if ((dst[2] << 16 | dst[1] << 8 | dst[0]) != (src[0] << 16 | src[1] << 8 | src[2])) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test4");
return nret;
}
static int
test5(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_BGR555;
unsigned char src[] = { 0x23, 0xc8 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if ((dst[2] >> 3 << 10 | dst[1] >> 3 << 5 | dst[0] >> 3) != (src[0] << 8 | src[1])) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test5");
return nret;
}
static int
test6(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_BGR565;
unsigned char src[] = { 0x47, 0x88 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if ((dst[2] >> 3 << 11 | dst[1] >> 2 << 5 | dst[0] >> 3) != (src[0] << 8 | src[1])) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test6");
return nret;
}
static int
test7(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_AG88;
unsigned char src[] = { 0x47, 0x88 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if (dst[0] != src[1]) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test7");
return nret;
}
static int
test8(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_GA88;
unsigned char src[] = { 0x47, 0x88 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if (dst[0] != src[0]) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test8");
return nret;
}
static int
test9(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_RGBA8888;
unsigned char src[] = { 0x46, 0xf3, 0xe5, 0xf0 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if (dst[0] != src[0]) {
goto error;
}
if (dst[1] != src[1]) {
goto error;
}
if (dst[2] != src[2]) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test8");
return nret;
}
static int
test10(void)
{
unsigned char dst[3];
int dst_pixelformat = SIXEL_PIXELFORMAT_RGB888;
int src_pixelformat = SIXEL_PIXELFORMAT_ARGB8888;
unsigned char src[] = { 0x46, 0xf3, 0xe5, 0xf0 };
int ret = 0;
int nret = EXIT_FAILURE;
ret = sixel_helper_normalize_pixelformat(dst,
&dst_pixelformat,
src,
src_pixelformat,
1,
1);
if (ret != 0) {
goto error;
}
if (dst_pixelformat != SIXEL_PIXELFORMAT_RGB888) {
goto error;
}
if (dst[0] != src[1]) {
goto error;
}
if (dst[1] != src[2]) {
goto error;
}
if (dst[2] != src[3]) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test8");
return nret;
}
SIXELAPI int
sixel_pixelformat_tests_main(void)
{
int nret = EXIT_FAILURE;
size_t i;
typedef int (* testcase)(void);
static testcase const testcases[] = {
test1,
test2,
test3,
test4,
test5,
test6,
test7,
test8,
test9,
test10,
};
for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
nret = testcases[i]();
if (nret != EXIT_SUCCESS) {
goto error;
}
}
nret = EXIT_SUCCESS;
error:
return nret;
}
#endif /* HAVE_TESTS */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+1549
View File
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2014-2016 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBSIXEL_QUANT_H
#define LIBSIXEL_QUANT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <sixel.h>
/* choose colors using median-cut method */
SIXELSTATUS
sixel_quant_make_palette(
unsigned char /* out */ **result,
unsigned const char /* in */ *data, /* data for sampling */
unsigned int /* in */ length, /* data size */
int /* in */ pixelformat,
unsigned int /* in */ reqcolors,
unsigned int /* in */ *ncolors,
unsigned int /* in */ *origcolors,
int /* in */ methodForLargest,
int /* in */ methodForRep,
int /* in */ qualityMode,
sixel_allocator_t /* in */ *allocator);
/* apply color palette into specified pixel buffers */
SIXELSTATUS
sixel_quant_apply_palette(
sixel_index_t /* out */ *result,
unsigned char /* in */ *data,
int /* in */ width,
int /* in */ height,
int /* in */ pixelformat,
unsigned char /* in */ *palette,
int /* in */ reqcolor,
int const /* in */ methodForDiffuse,
int /* in */ foptimize,
int /* in */ foptimize_palette,
int /* in */ complexion,
unsigned short /* in */ *cachetable,
int /* in */ *ncolor,
sixel_allocator_t /* in */ *allocator);
/* deallocate specified palette */
void
sixel_quant_free_palette(
unsigned char /* in */ *data,
sixel_allocator_t /* in */ *allocator);
#if HAVE_TESTS
int
sixel_quant_tests_main(void);
#endif
#ifdef __cplusplus
}
#endif
#endif /* LIBSIXEL_QUANT_H */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+12
View File
@@ -0,0 +1,12 @@
{
"home": "https://github.com/saitoha/libsixel",
"license": "MIT ( embed in source )",
"version": "1.8.7-r2",
"author": "Hayaki Saito",
"modifications": [
"sixel.h: SIXELAPI is empty, so the statically linked subset exports nothing",
"config.h: hand-written, because only the vendored files read it",
"tosixel.c: DCS P2 initialised to 1, so unset pixels are not filled with colour-table entry 0",
"output.c: release the allocator reference if output-context allocation fails"
]
}
+1173
View File
File diff suppressed because it is too large Load Diff
+355
View File
@@ -0,0 +1,355 @@
/*
* Copyright (c) 2014-2018 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
/* STDC_HEADERS */
#include <stdio.h>
#include <stdlib.h>
#if HAVE_MEMORY_H
# include <memory.h>
#endif /* HAVE_MEMORY_H */
#ifdef HAVE_STRING_H
# include <string.h>
#endif /* HAVE_STRING_H */
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif /* HAVE_ERRNO_H */
#ifdef HAVE_LIBCURL
# include <curl/curl.h>
#endif /* HAVE_LIBCURL */
#include <sixel.h>
#include "status.h"
#define SIXEL_MESSAGE_OK ("succeeded")
#define SIXEL_MESSAGE_FALSE ("unexpected error (SIXEL_FALSE)");
#define SIXEL_MESSAGE_UNEXPECTED ("unexpected error")
#define SIXEL_MESSAGE_INTERRUPTED ("interrupted by a signal")
#define SIXEL_MESSAGE_BAD_ALLOCATION ("runtime error: bad allocation error")
#define SIXEL_MESSAGE_BAD_ARGUMENT ("runtime error: bad argument detected")
#define SIXEL_MESSAGE_BAD_INPUT ("runtime error: bad input detected")
#define SIXEL_MESSAGE_BAD_INTEGER_OVERFLOW ("runtime error: integer overflow")
#define SIXEL_MESSAGE_RUNTIME_ERROR ("runtime error")
#define SIXEL_MESSAGE_LOGIC_ERROR ("logic error")
#define SIXEL_MESSAGE_NOT_IMPLEMENTED ("feature error: not implemented")
#define SIXEL_MESSAGE_FEATURE_ERROR ("feature error")
#define SIXEL_MESSAGE_STBI_ERROR ("stb_image error")
#define SIXEL_MESSAGE_STBIW_ERROR ("stb_image_write error")
#define SIXEL_MESSAGE_JPEG_ERROR ("libjpeg error")
#define SIXEL_MESSAGE_PNG_ERROR ("libpng error")
#define SIXEL_MESSAGE_GDK_ERROR ("GDK error")
#define SIXEL_MESSAGE_GD_ERROR ("GD error")
static char g_buffer[1024] = { 0x0 };
/* set detailed error message (thread-unsafe) */
SIXELAPI void
sixel_helper_set_additional_message(
const char /* in */ *message /* error message */
)
{
size_t len;
if (message == 0)
return;
len = strlen(message);
memcpy(g_buffer, message, len < sizeof(g_buffer) ? len: sizeof(g_buffer) - 1);
g_buffer[sizeof(g_buffer) - 1] = 0;
}
/* get detailed error message (thread-unsafe) */
SIXELAPI char const *
sixel_helper_get_additional_message(void)
{
return g_buffer;
}
/* convert error status code int formatted string */
SIXELAPI char const *
sixel_helper_format_error(
SIXELSTATUS /* in */ status /* status code */
)
{
static char buffer[1024];
char const *error_string;
char *p;
size_t len;
switch (status & 0x1000) {
case SIXEL_OK:
switch (status) {
case SIXEL_INTERRUPTED:
error_string = SIXEL_MESSAGE_INTERRUPTED;
break;
case SIXEL_OK:
default:
error_string = SIXEL_MESSAGE_OK;
break;
}
break;
case SIXEL_FALSE:
switch (status & 0x1f00) {
case SIXEL_RUNTIME_ERROR:
switch (status) {
case SIXEL_BAD_ALLOCATION:
error_string = SIXEL_MESSAGE_BAD_ALLOCATION;
break;
case SIXEL_BAD_ARGUMENT:
error_string = SIXEL_MESSAGE_BAD_ARGUMENT;
break;
case SIXEL_BAD_INPUT:
error_string = SIXEL_MESSAGE_BAD_INPUT;
break;
case SIXEL_BAD_INTEGER_OVERFLOW:
error_string = SIXEL_MESSAGE_BAD_INTEGER_OVERFLOW;
break;
default:
error_string = SIXEL_MESSAGE_RUNTIME_ERROR;
break;
}
break;
case SIXEL_LOGIC_ERROR:
error_string = SIXEL_MESSAGE_LOGIC_ERROR;
break;
case SIXEL_FEATURE_ERROR:
switch (status) {
case SIXEL_NOT_IMPLEMENTED:
error_string = SIXEL_MESSAGE_NOT_IMPLEMENTED;
break;
default:
error_string = SIXEL_MESSAGE_FEATURE_ERROR;
break;
}
break;
case SIXEL_LIBC_ERROR:
p = strerror(errno);
len = strlen(p) + 1;
memcpy(buffer, p, len < sizeof(buffer) ? len: sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = 0;
error_string = buffer;
break;
#ifdef HAVE_LIBCURL
case SIXEL_CURL_ERROR:
error_string = curl_easy_strerror(status & 0xff);
break;
#endif
#ifdef HAVE_JPEG
case SIXEL_JPEG_ERROR:
error_string = SIXEL_MESSAGE_JPEG_ERROR;
break;
#endif
#ifdef HAVE_LIBPNG
case SIXEL_PNG_ERROR:
error_string = SIXEL_MESSAGE_PNG_ERROR;
break;
#endif
#ifdef HAVE_GDK_PIXBUF2
case SIXEL_GDK_ERROR:
error_string = SIXEL_MESSAGE_GDK_ERROR;
break;
#endif
#ifdef HAVE_GD
case SIXEL_GD_ERROR:
error_string = SIXEL_MESSAGE_GD_ERROR;
break;
#endif
case SIXEL_STBI_ERROR:
error_string = SIXEL_MESSAGE_STBI_ERROR;
break;
case SIXEL_STBIW_ERROR:
error_string = SIXEL_MESSAGE_STBIW_ERROR;
break;
case SIXEL_FALSE:
error_string = SIXEL_MESSAGE_FALSE;
break;
default:
error_string = SIXEL_MESSAGE_UNEXPECTED;
break;
}
break;
default:
error_string = SIXEL_MESSAGE_UNEXPECTED;
break;
}
return error_string;
}
#if HAVE_TESTS
static int
test1(void)
{
int nret = EXIT_FAILURE;
char const *message;
message = sixel_helper_format_error(SIXEL_OK);
if (strcmp(message, SIXEL_MESSAGE_OK) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_INTERRUPTED);
if (strcmp(message, SIXEL_MESSAGE_INTERRUPTED) != 0) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test1");
return nret;
}
static int
test2(void)
{
int nret = EXIT_FAILURE;
char const *message;
message = sixel_helper_format_error(SIXEL_BAD_ALLOCATION);
if (strcmp(message, SIXEL_MESSAGE_BAD_ALLOCATION) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_BAD_ARGUMENT);
if (strcmp(message, SIXEL_MESSAGE_BAD_ARGUMENT) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_BAD_INPUT);
if (strcmp(message, SIXEL_MESSAGE_BAD_INPUT) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_RUNTIME_ERROR);
if (strcmp(message, SIXEL_MESSAGE_RUNTIME_ERROR) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_LOGIC_ERROR);
if (strcmp(message, SIXEL_MESSAGE_LOGIC_ERROR) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_NOT_IMPLEMENTED);
if (strcmp(message, SIXEL_MESSAGE_NOT_IMPLEMENTED) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_FEATURE_ERROR);
if (strcmp(message, SIXEL_MESSAGE_FEATURE_ERROR) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_LIBC_ERROR);
if (strcmp(message, SIXEL_MESSAGE_UNEXPECTED) == 0) {
goto error;
}
#ifdef HAVE_LIBCURL
message = sixel_helper_format_error(SIXEL_CURL_ERROR);
if (strcmp(message, SIXEL_MESSAGE_UNEXPECTED) == 0) {
goto error;
}
#endif
#if HAVE_JPEG
message = sixel_helper_format_error(SIXEL_JPEG_ERROR);
if (strcmp(message, SIXEL_MESSAGE_JPEG_ERROR) != 0) {
goto error;
}
#endif
#if HAVE_LIBPNG
message = sixel_helper_format_error(SIXEL_PNG_ERROR);
if (strcmp(message, SIXEL_MESSAGE_PNG_ERROR) != 0) {
goto error;
}
#endif
#if HAVE_GD
message = sixel_helper_format_error(SIXEL_GD_ERROR);
if (strcmp(message, SIXEL_MESSAGE_GD_ERROR) != 0) {
goto error;
}
#endif
#if HAVE_GDK_PIXBUF2
message = sixel_helper_format_error(SIXEL_GDK_ERROR);
if (strcmp(message, SIXEL_MESSAGE_GDK_ERROR) != 0) {
goto error;
}
#endif
message = sixel_helper_format_error(SIXEL_STBI_ERROR);
if (strcmp(message, SIXEL_MESSAGE_STBI_ERROR) != 0) {
goto error;
}
message = sixel_helper_format_error(SIXEL_STBIW_ERROR);
if (strcmp(message, SIXEL_MESSAGE_STBIW_ERROR) != 0) {
goto error;
}
return EXIT_SUCCESS;
error:
perror("test2");
return nret;
}
SIXELAPI int
sixel_status_tests_main(void)
{
int nret = EXIT_FAILURE;
size_t i;
typedef int (* testcase)(void);
static testcase const testcases[] = {
test1,
test2,
};
for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
nret = testcases[i]();
if (nret != EXIT_SUCCESS) {
goto error;
}
}
nret = EXIT_SUCCESS;
error:
return nret;
}
#endif /* HAVE_TESTS */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2014-2016 Hayaki Saito
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef LIBSIXEL_STATUS_H
#define LIBSIXEL_STATUS_H
#ifdef __cplusplus
extern "C" {
#endif
#if HAVE_TESTS
int
sixel_status_tests_main(void);
#endif
#ifdef __cplusplus
}
#endif
#endif /* LIBSIXEL_STATUS_H */
/* emacs Local Variables: */
/* emacs mode: c */
/* emacs tab-width: 4 */
/* emacs indent-tabs-mode: nil */
/* emacs c-basic-offset: 4 */
/* emacs End: */
/* vim: set expandtab ts=4 sts=4 sw=4 : */
/* EOF */
+1635
View File
File diff suppressed because it is too large Load Diff
+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 -1
View File
@@ -8,7 +8,10 @@ typedef struct FFPlatformSysinfo {
FFstrbuf release;
FFstrbuf version;
FFstrbuf architecture;
uint32_t pageSize;
// The base-2 logarithm of the memory page size, i.e. a page is `1 << pageSizeShift` bytes.
// Page sizes are powers of two on every supported platform. Storing the exponent instead of
// the byte count keeps page count arithmetic in 64 bits and makes the invariant explicit.
uint32_t pageSizeShift;
} FFPlatformSysinfo;
typedef struct FFPlatform {
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "fastfetch.h"
// A cache for a value that is built at most once per generation.
//
// The value itself is owned by the entry owner (usually a file scope `static`); the entry only
// remembers whether that value is still valid and knows how to drop it. `--dynamic-interval`
// starts a new generation for every round, dropping all the values that have been built so far,
// so the modules re-detect them instead of replaying the first round's snapshot.
//
// An entry registers itself the first time it is used. There is therefore no central list to keep
// in sync, and a cache cannot be forgotten in it. Do not use this type for state that is carried
// across calls to derive a rate (cpuusage, diskio, netio, top): that is a baseline rather than a
// snapshot, and dropping it makes the first sample of a round read as zero.
//
// The storage pointed to by `storage` must stay valid for the whole run; only the value behind it
// is dropped by an invalidation.
//
// Not thread safe: entries are only touched from a single thread, on a frame boundary, while the
// modules are not running.
typedef struct FFcacheEntry {
const char* name; // For diagnostics.
void* storage; // Owned by the entry owner. May be nullptr.
void (*init)(void* storage); // Optional. Runs whenever the entry is stale.
void (*destroy)(void* storage); // Optional. Must be idempotent, and tolerate a partially initialized storage.
bool initialized;
bool registered;
struct FFcacheEntry* next; // Internal: registry link.
} FFcacheEntry;
// Returns `entry->storage`, running `init` on it first when the entry is stale. The entry is marked
// initialized before `init` runs, so a call made from within `init` returns the partially built
// value instead of recursing.
[[gnu::nonnull(1)]] void* ffCacheGet(FFcacheEntry* entry);
// Staleness test for entries whose initialization needs arguments that are not part of the cache
// identity (such as the cover file request of `ffDetectMedia`) and therefore cannot live behind
// `init(void*)`. Returns true exactly once per generation: the caller must then build the value
// itself.
[[gnu::nonnull(1), nodiscard]] bool ffCacheBeginInit(FFcacheEntry* entry);
// Drops the cached value of one entry, so that the next request builds it again. Idempotent, and
// safe on an entry that has never been used.
[[gnu::nonnull(1)]] void ffCacheInvalidate(FFcacheEntry* entry);
// Drops every value that has been built so far. Called once per `--dynamic-interval` round.
void ffCacheInvalidateAll(void);
+18 -15
View File
@@ -15,17 +15,18 @@ typedef struct FFlist {
} FFlist;
// Removes the first element, and copy its value to `*result`
bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result);
// `result` is only written when the list is not empty, so it may be null for an empty list
[[gnu::nonnull(1), nodiscard]] bool ffListShift(FFlist* list, uint32_t elementSize, void* __restrict result);
// Removes the last element, and copy its value to `*result`
bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result);
[[gnu::nonnull(1), nodiscard]] bool ffListPop(FFlist* list, uint32_t elementSize, void* __restrict result);
static inline void ffListInit(FFlist* list) {
[[gnu::nonnull(1)]] static inline void ffListInit(FFlist* list) {
list->capacity = 0;
list->length = 0;
list->data = nullptr;
}
static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity) {
[[gnu::nonnull(1)]] static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capacity) {
ffListInit(list);
list->capacity = capacity;
list->data = __builtin_expect(capacity == 0, 0) ? nullptr : (uint8_t*) malloc((size_t) capacity * elementSize);
@@ -43,12 +44,13 @@ static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capa
return result;
}
[[nodiscard]] static inline void* ffListGet(const FFlist* list, uint32_t elementSize, uint32_t index) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline void* ffListGet(const FFlist* list, uint32_t elementSize, uint32_t index) {
assert(list->capacity > index);
return list->data + (index * elementSize);
}
[[nodiscard]] static inline uint32_t ffListFirstIndexComp(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) {
// Not `pure`: `compFunc` is caller-supplied and may have side effects
[[gnu::nonnull(1, 3, 4), nodiscard]] static inline uint32_t ffListFirstIndexComp(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) {
for (uint32_t i = 0; i < list->length; i++) {
if (compFunc(ffListGet(list, elementSize, i), compElement)) {
return i;
@@ -58,16 +60,17 @@ static inline void ffListInitA(FFlist* list, uint32_t elementSize, uint32_t capa
return list->length;
}
[[nodiscard]] static inline bool ffListContains(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) {
// Not `pure`, same reason as ffListFirstIndexComp
[[gnu::nonnull(1, 3, 4), nodiscard]] static inline bool ffListContains(const FFlist* list, uint32_t elementSize, void* compElement, bool (*compFunc)(const void*, const void*)) {
return ffListFirstIndexComp(list, elementSize, compElement, compFunc) != list->length;
}
static inline void ffListSort(FFlist* list, uint32_t elementSize, int (*compar)(const void*, const void*)) {
[[gnu::nonnull(1, 3)]] static inline void ffListSort(FFlist* list, uint32_t elementSize, int (*compar)(const void*, const void*)) {
qsort(list->data, list->length, elementSize, compar);
}
// Move the contents of `src` into `list`, and left `src` empty
static inline void ffListInitMove(FFlist* list, FFlist* src) {
[[gnu::nonnull(1)]] static inline void ffListInitMove(FFlist* list, FFlist* src) {
if (src) {
list->capacity = src->capacity;
list->length = src->length;
@@ -78,7 +81,7 @@ static inline void ffListInitMove(FFlist* list, FFlist* src) {
}
}
static inline void ffListDestroy(FFlist* list) {
[[gnu::nonnull(1)]] static inline void ffListDestroy(FFlist* list) {
if (!list->data) {
return;
}
@@ -89,11 +92,11 @@ static inline void ffListDestroy(FFlist* list) {
list->data = nullptr;
}
static inline void ffListClear(FFlist* list) {
[[gnu::nonnull(1)]] static inline void ffListClear(FFlist* list) {
list->length = 0;
}
static inline void ffListReserve(FFlist* list, uint32_t elementSize, uint32_t newCapacity) {
[[gnu::nonnull(1)]] static inline void ffListReserve(FFlist* list, uint32_t elementSize, uint32_t newCapacity) {
if (__builtin_expect(newCapacity <= list->capacity, false)) {
return;
}
@@ -102,7 +105,7 @@ static inline void ffListReserve(FFlist* list, uint32_t elementSize, uint32_t ne
list->capacity = newCapacity;
}
static inline void* ffListAdd(FFlist* list, uint32_t elementSize) {
[[gnu::nonnull(1)]] static inline void* ffListAdd(FFlist* list, uint32_t elementSize) {
if (__builtin_expect(list->length == list->capacity, false)) {
ffListReserve(list, elementSize, list->capacity == 0 ? FF_LIST_DEFAULT_ALLOC : list->capacity * 2);
}
@@ -111,13 +114,13 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) {
return ffListGet(list, elementSize, list->length - 1);
}
static inline void ffListRemoveAt(FFlist* list, uint32_t elementSize, uint32_t index) {
[[gnu::nonnull(1)]] static inline void ffListRemoveAt(FFlist* list, uint32_t elementSize, uint32_t index) {
assert(list->length > index);
memmove(list->data + (index * elementSize), list->data + ((index + 1) * elementSize), (size_t) (list->length - index - 1) * elementSize);
--list->length;
}
static inline void ffListInsertAt(FFlist* list, uint32_t elementSize, uint32_t index, const void* element) {
[[gnu::nonnull(1, 4)]] static inline void ffListInsertAt(FFlist* list, uint32_t elementSize, uint32_t index, const void* element) {
assert(list->length >= index);
ffListAdd(list, elementSize);
memmove(list->data + ((index + 1) * elementSize), list->data + (index * elementSize), (size_t) (list->length - index - 1) * elementSize);
+164 -132
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>
@@ -23,6 +24,29 @@ __stdcall char* StrStrIA(const char* lpFirst, const char* lpSrch);
#define FASTFETCH_STRBUF_DEFAULT_ALLOC 32
// ---------------------------------------------------------------------------------------------
// Contract attributes used throughout this header:
//
// `gnu::pure` marks a function that reads memory (through its arguments and through globals) but
// writes nothing observable, so the compiler may cache and reorder calls to it. It is applied to
// the comparison / search accessors, and deliberately NOT to:
// * the `To*` converters -- `strtod` / `strtoull` / `strtoll` write `errno`;
// * `ffStrbufEndsWithFn` -- the caller-supplied `fn` may have side effects;
// * `ffStrbufWriteTo` / `ffStrbufPutTo` -- they write to a `FILE*`.
//
// `gnu::nonnull(N)` is added wherever the function dereferences argument N unconditionally. The
// two cannot be combined with a null check: clang reports `-Wtautological-pointer-compare` /
// `-Wpointer-bool-conversion` (both in `-Wall`) when a `nonnull` parameter is compared against
// null, so a redundant `assert(p != nullptr)` is dropped in favour of the attribute. Asserts that
// state something `nonnull` cannot express -- pointer aliasing (`value != strbuf`), index ranges
// (`start <= strbuf->length`), or a non-pointer invariant -- are kept.
//
// `nodiscard` is NOT applied to the "was the buffer modified" boolean returns
// (`ffStrbufSubstr*`, `ffStrbufRemoveSubstr`, `ffStrbufEnsureEndsWithC`, ...). Ignoring that
// result is a normal, intended use of those functions -- `ffStrbufSubstrBefore` alone has 153 call
// sites and ~147 of them discard the result -- so `nodiscard` would only produce noise.
// ---------------------------------------------------------------------------------------------
// static string (allocated == 0), chars points to a string literal
// dynamic string (allocated > 0), chars points to a heap allocated buffer
typedef struct FFstrbuf {
@@ -31,67 +55,70 @@ typedef struct FFstrbuf {
char* chars;
} FFstrbuf;
static inline void ffStrbufInit(FFstrbuf* strbuf);
void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate);
void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments);
void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr);
[[gnu::format(printf, 2, 3)]] void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...);
[[gnu::format(printf, 1, 2)]] [[nodiscard]] FFstrbuf ffStrbufCreateF(const char* format, ...);
[[gnu::nonnull(1)]] static inline void ffStrbufInit(FFstrbuf* strbuf);
[[gnu::nonnull(1)]] void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate);
[[gnu::nonnull(1, 2), gnu::format(printf, 2, 0)]] void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments);
[[gnu::nonnull(1, 3)]] void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr);
[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufInitF(FFstrbuf* strbuf, const char* format, ...);
[[gnu::format(printf, 1, 2), gnu::nonnull(1), nodiscard]] FFstrbuf ffStrbufCreateF(const char* format, ...);
void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free);
void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free);
[[gnu::nonnull(1)]] void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free);
[[gnu::nonnull(1)]] void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free);
static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value);
void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int));
[[gnu::format(printf, 2, 3)]] void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...);
void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments);
const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until);
[[gnu::nonnull(1)]] static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value);
[[gnu::nonnull(1, 3)]] void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transformFunc)(int));
[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...);
[[gnu::nonnull(1, 2), gnu::format(printf, 2, 0)]] void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments);
// Returns the pointer to the terminator, or nullptr if `value` was nullptr; callers are free to
// ignore it, so not `nodiscard`.
[[gnu::nonnull(1)]] const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char until);
void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value);
void ffStrbufPrependC(FFstrbuf* strbuf, char c);
[[gnu::nonnull(1)]] void ffStrbufPrependNS(FFstrbuf* strbuf, uint32_t length, const char* value);
[[gnu::nonnull(1)]] void ffStrbufPrependC(FFstrbuf* strbuf, char c);
void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c);
[[gnu::nonnull(1)]] void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c);
// Clear the content of strbuf and set new value
// NOTE: Unlike ffStrbufAppend*, ffStrbufSet* functions may NOT reserve extra space
void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value);
void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value);
[[gnu::format(printf, 2, 3)]] void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...);
[[gnu::nonnull(1, 2)]] void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value);
[[gnu::nonnull(1, 3)]] void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value);
[[gnu::format(printf, 2, 3), gnu::nonnull(1, 2)]] void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...);
void ffStrbufTrimLeft(FFstrbuf* strbuf, char c);
void ffStrbufTrimRight(FFstrbuf* strbuf, char c);
void ffStrbufTrimLeftSpace(FFstrbuf* strbuf);
void ffStrbufTrimRightSpace(FFstrbuf* strbuf);
[[gnu::nonnull(1)]] void ffStrbufTrimLeft(FFstrbuf* strbuf, char c);
[[gnu::nonnull(1)]] void ffStrbufTrimRight(FFstrbuf* strbuf, char c);
[[gnu::nonnull(1)]] void ffStrbufTrimLeftSpace(FFstrbuf* strbuf);
[[gnu::nonnull(1)]] void ffStrbufTrimRightSpace(FFstrbuf* strbuf);
bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex);
void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str);
void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]);
[[gnu::nonnull(1)]] bool ffStrbufRemoveSubstr(FFstrbuf* strbuf, uint32_t startIndex, uint32_t endIndex);
[[gnu::nonnull(1, 2)]] void ffStrbufRemoveS(FFstrbuf* strbuf, const char* str);
// `strings` is only dereferenced when `numStrings > 0`, so it is intentionally not `nonnull(3)`
[[gnu::nonnull(1)]] void ffStrbufRemoveStrings(FFstrbuf* strbuf, uint32_t numStrings, const char* strings[]);
void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace);
[[gnu::nonnull(1)]] void ffStrbufReplaceAllC(FFstrbuf* strbuf, char find, char replace);
// Returns true if the strbuf is modified
bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index);
bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index
bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c);
bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str);
bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c);
bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end);
[[gnu::nonnull(1)]] bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index);
[[gnu::nonnull(1)]] bool ffStrbufSubstrAfter(FFstrbuf* strbuf, uint32_t index); // Not including the index
[[gnu::nonnull(1)]] bool ffStrbufSubstrAfterFirstC(FFstrbuf* strbuf, char c);
[[gnu::nonnull(1, 2)]] bool ffStrbufSubstrAfterFirstS(FFstrbuf* strbuf, const char* str);
[[gnu::nonnull(1)]] bool ffStrbufSubstrAfterLastC(FFstrbuf* strbuf, char c);
[[gnu::nonnull(1)]] bool ffStrbufSubstr(FFstrbuf* strbuf, uint32_t start, uint32_t end);
[[nodiscard]] uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c);
[[gnu::nonnull(1), gnu::pure, nodiscard]] uint32_t ffStrbufCountC(const FFstrbuf* strbuf, char c);
bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end);
[[gnu::nonnull(1, 2)]] bool ffStrbufRemoveIgnCaseEndS(FFstrbuf* strbuf, const char* end);
bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c);
[[gnu::nonnull(1)]] bool ffStrbufEnsureEndsWithC(FFstrbuf* strbuf, char c);
void ffStrbufUpperCase(FFstrbuf* strbuf);
void ffStrbufLowerCase(FFstrbuf* strbuf);
[[gnu::nonnull(1)]] void ffStrbufUpperCase(FFstrbuf* strbuf);
[[gnu::nonnull(1)]] void ffStrbufLowerCase(FFstrbuf* strbuf);
// Function alters the buffer to extract lines or delimited segments (replaces the delimiter with '\0')
// so that buffer MUST be heap allocated (NOT a static string)
// `lineptr` must be `nullptr` and `n` MUST be `0` for the first call
// Caller MUST NOT free `*lineptr`
bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);
void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);
[[gnu::nonnull(1, 2, 4)]] bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);
[[gnu::nonnull(1, 2, 4)]] void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer);
/**
* @brief Read a line from a FFstrbuf.
@@ -109,29 +136,31 @@ void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf
*
* @return true if a line has been read, false if the end of the buffer has been reached.
*/
static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) {
[[gnu::nonnull(1, 2, 3), nodiscard]] static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) {
return ffStrbufGetdelim(lineptr, n, '\n', buffer);
}
/**
* @brief Restore the end of a line that was modified by ffStrbufGetline.
* @warning This function should be called before breaking an ffStrbufGetline loop if `buffer` will be used later.
*/
static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) {
[[gnu::nonnull(1, 2, 3)]] static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) {
ffStrbufGetdelimRestore(lineptr, n, '\n', buffer);
}
bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf);
bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
[[gnu::nonnull(1)]] bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf);
// `comp` is only dereferenced when `compLength > 0` in the first pair, and is never dereferenced
// when `strbuf` is empty in the second pair, so neither takes `nonnull(3)`
[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
[[gnu::nonnull(1), gnu::pure, nodiscard]] bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator);
int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint);
[[gnu::nonnull(1)]] int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint);
void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value);
void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value);
[[gnu::nonnull(1)]] void ffStrbufAppendSInt(FFstrbuf* strbuf, int64_t value);
[[gnu::nonnull(1)]] void ffStrbufAppendUInt(FFstrbuf* strbuf, uint64_t value);
// Appends a double value to the string buffer with the specified precision (0~15).
// if `precision < 0`, let yyjson decide the precision
void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros);
[[gnu::nonnull(1)]] void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool trailingZeros);
[[nodiscard]] static inline FFstrbuf ffStrbufCreateA(uint32_t allocate) {
FFstrbuf strbuf;
@@ -139,7 +168,7 @@ void ffStrbufAppendDouble(FFstrbuf* strbuf, double value, int8_t precision, bool
return strbuf;
}
static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src) {
[[gnu::nonnull(1, 2)]] static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict src) {
if (src->allocated == 0) { // static string
*strbuf = *src;
} else {
@@ -148,14 +177,14 @@ static inline void ffStrbufInitCopy(FFstrbuf* __restrict strbuf, const FFstrbuf*
}
}
[[nodiscard]] static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src) {
[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffStrbufCreateCopy(const FFstrbuf* src) {
FFstrbuf strbuf;
ffStrbufInitCopy(&strbuf, src);
return strbuf;
}
// Move the content of `src` into `strbuf`, and left `src` empty
static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) {
[[gnu::nonnull(1)]] static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) {
if (src) {
*strbuf = *src;
ffStrbufInit(src);
@@ -170,12 +199,12 @@ static inline void ffStrbufInitMove(FFstrbuf* strbuf, FFstrbuf* src) {
return strbuf;
}
static inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr) {
[[gnu::nonnull(1, 2)]] static inline void ffStrbufInitMoveS(FFstrbuf* strbuf, char* heapStr) {
ffStrbufInitMoveNS(strbuf, (uint32_t) strlen(heapStr), heapStr);
}
// Despite the name, this function resets strbuf to the initial/unallocated state
static inline void ffStrbufDestroy(FFstrbuf* strbuf) {
[[gnu::nonnull(1)]] static inline void ffStrbufDestroy(FFstrbuf* strbuf) {
if (strbuf->allocated > 0) {
free(strbuf->chars);
}
@@ -183,8 +212,7 @@ static inline void ffStrbufDestroy(FFstrbuf* strbuf) {
ffStrbufInit(strbuf);
}
[[nodiscard]] static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf) {
assert(strbuf != nullptr);
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufGetFree(const FFstrbuf* strbuf) {
if (strbuf->allocated == 0) {
return 0;
}
@@ -192,7 +220,7 @@ static inline void ffStrbufDestroy(FFstrbuf* strbuf) {
return strbuf->allocated - strbuf->length - 1; // - 1 for the null byte
}
static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) {
[[gnu::nonnull(1)]] static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) {
if (__builtin_expect(free == 0, false)) {
if (__builtin_expect(!(strbuf->allocated == 0 && strbuf->length > 0), true)) {
return;
@@ -207,8 +235,7 @@ static inline void ffStrbufEnsureFree(FFstrbuf* strbuf, uint32_t free) {
}
static inline void ffStrbufClear(FFstrbuf* strbuf) {
assert(strbuf != nullptr);
[[gnu::nonnull(1)]] static inline void ffStrbufClear(FFstrbuf* strbuf) {
extern char* CHAR_NULL_PTR;
if (strbuf->allocated == 0) {
@@ -220,13 +247,13 @@ static inline void ffStrbufClear(FFstrbuf* strbuf) {
strbuf->length = 0;
}
static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1)]] static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) {
ffStrbufEnsureFree(strbuf, 1);
strbuf->chars[strbuf->length++] = c;
strbuf->chars[strbuf->length] = '\0';
}
static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) {
[[gnu::nonnull(1)]] static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) {
if (__builtin_expect(num == 0, false)) {
return;
}
@@ -237,7 +264,7 @@ static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) {
strbuf->chars[strbuf->length] = '\0';
}
static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
[[gnu::nonnull(1)]] static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
if (__builtin_expect(value == nullptr || length == 0, false)) {
return;
}
@@ -248,7 +275,7 @@ static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const cha
strbuf->chars[strbuf->length] = '\0';
}
static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value) {
[[gnu::nonnull(1)]] static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* __restrict value) {
assert(value != strbuf);
if (value == nullptr) {
return;
@@ -256,13 +283,12 @@ static inline void ffStrbufAppend(FFstrbuf* __restrict strbuf, const FFstrbuf* _
ffStrbufAppendNS(strbuf, value->length, value->chars);
}
static inline void ffStrbufRecalculateLength(FFstrbuf* strbuf) {
[[gnu::nonnull(1)]] static inline void ffStrbufRecalculateLength(FFstrbuf* strbuf) {
strbuf->length = (uint32_t) strlen(strbuf->chars);
}
static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) {
assert(strbuf != nullptr);
// `value` may be null (clears the buffer); `strbuf` may not
[[gnu::nonnull(1)]] static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) {
if (value == nullptr) {
ffStrbufClear(strbuf);
} else {
@@ -270,9 +296,7 @@ static inline void ffStrbufSetS(FFstrbuf* strbuf, const char* value) {
}
}
static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
assert(strbuf != nullptr);
[[gnu::nonnull(1)]] static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
if (yyjson_is_str(jsonVal)) {
ffStrbufSetNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal));
return true;
@@ -282,13 +306,15 @@ static inline bool ffStrbufSetJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
return false;
}
static inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value) {
[[gnu::nonnull(1)]] static inline void ffStrbufAppendS(FFstrbuf* strbuf, const char* value) {
if (value == nullptr) {
return;
}
ffStrbufAppendNS(strbuf, (uint32_t) strlen(value), value);
}
// Returns whether `jsonVal` was a string. Callers routinely pre-check with `yyjson_is_str`, so the
// result is not `nodiscard`.
static inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
if (yyjson_is_str(jsonVal)) {
ffStrbufAppendNS(strbuf, (uint32_t) unsafe_yyjson_get_len(jsonVal), unsafe_yyjson_get_str(jsonVal));
@@ -297,7 +323,7 @@ static inline bool ffStrbufAppendJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal)
return false;
}
static inline void ffStrbufInit(FFstrbuf* strbuf) {
[[gnu::nonnull(1)]] static inline void ffStrbufInit(FFstrbuf* strbuf) {
extern char* CHAR_NULL_PTR;
strbuf->allocated = strbuf->length = 0;
strbuf->chars = CHAR_NULL_PTR;
@@ -326,7 +352,7 @@ static inline void ffStrbufInitStatic(FFstrbuf* strbuf, const char* str) {
return strbuf;
}
static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) {
[[gnu::nonnull(1)]] static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) {
if (strbuf->allocated > 0) {
free(strbuf->chars);
}
@@ -338,7 +364,7 @@ static inline void ffStrbufSetStatic(FFstrbuf* strbuf, const char* value) {
}
}
static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str) {
[[gnu::nonnull(1)]] static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char* str) {
ffStrbufInit(strbuf);
ffStrbufAppendNS(strbuf, length, str);
}
@@ -349,12 +375,13 @@ static inline void ffStrbufInitNS(FFstrbuf* strbuf, uint32_t length, const char*
return strbuf;
}
// Returns whether `jsonVal` was a string; not `nodiscard`, same reason as ffStrbufAppendJsonVal
static inline bool ffStrbufInitJsonVal(FFstrbuf* strbuf, yyjson_val* jsonVal) {
ffStrbufInit(strbuf);
return ffStrbufAppendJsonVal(strbuf, jsonVal);
}
static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) {
[[gnu::nonnull(1)]] static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) {
ffStrbufInit(strbuf);
ffStrbufAppendS(strbuf, str);
}
@@ -365,107 +392,107 @@ static inline void ffStrbufInitS(FFstrbuf* strbuf, const char* str) {
return strbuf;
}
static inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value) {
[[gnu::nonnull(1)]] static inline void ffStrbufPrepend(FFstrbuf* strbuf, FFstrbuf* value) {
if (value == nullptr) {
return;
}
ffStrbufPrependNS(strbuf, value->length, value->chars);
}
static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) {
[[gnu::nonnull(1)]] static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) {
if (value == nullptr) {
return;
}
ffStrbufPrependNS(strbuf, (uint32_t) strlen(value), value);
}
[[nodiscard]] static inline int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufComp(const FFstrbuf* strbuf, const FFstrbuf* comp) {
uint32_t length = strbuf->length > comp->length ? comp->length : strbuf->length;
return memcmp(strbuf->chars, comp->chars, length + 1);
}
[[nodiscard]] static inline bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) {
return ffStrbufComp(strbuf, comp) == 0;
}
[[nodiscard]] static inline int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufCompS(const FFstrbuf* strbuf, const char* comp) {
return strcmp(strbuf->chars, comp);
}
[[nodiscard]] static inline bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEqualS(const FFstrbuf* strbuf, const char* comp) {
return ffStrbufCompS(strbuf, comp) == 0;
}
[[nodiscard]] static inline int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufIgnCaseCompS(const FFstrbuf* strbuf, const char* comp) {
return strcasecmp(strbuf->chars, comp);
}
[[nodiscard]] static inline bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufIgnCaseEqualS(const FFstrbuf* strbuf, const char* comp) {
return ffStrbufIgnCaseCompS(strbuf, comp) == 0;
}
[[nodiscard]] static inline int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline int ffStrbufIgnCaseComp(const FFstrbuf* strbuf, const FFstrbuf* comp) {
return ffStrbufIgnCaseCompS(strbuf, comp->chars);
}
[[nodiscard]] static inline bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufIgnCaseEqual(const FFstrbuf* strbuf, const FFstrbuf* comp) {
return ffStrbufIgnCaseComp(strbuf, comp) == 0;
}
[[nodiscard]] static inline bool ffStrbufContainC(const FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufContainC(const FFstrbuf* strbuf, char c) {
return memchr(strbuf->chars, c, strbuf->length) != nullptr;
}
[[nodiscard]] static inline bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainS(const FFstrbuf* strbuf, const char* str) {
return strstr(strbuf->chars, str) != nullptr;
}
[[nodiscard]] static inline bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContain(const FFstrbuf* strbuf, const FFstrbuf* str) {
return ffStrbufContainS(strbuf, str->chars);
}
[[nodiscard]] static inline bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainIgnCaseS(const FFstrbuf* strbuf, const char* str) {
return strcasestr(strbuf->chars, str) != nullptr;
}
[[nodiscard]] static inline bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* str) {
return ffStrbufContainIgnCaseS(strbuf, str->chars);
}
[[nodiscard]] static inline uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufNextIndexC(const FFstrbuf* strbuf, uint32_t start, char c) {
assert(start <= strbuf->length);
const char* ptr = (const char*) memchr(strbuf->chars + start, c, strbuf->length - start);
return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length;
}
[[nodiscard]] static inline uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str) {
[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline uint32_t ffStrbufNextIndexS(const FFstrbuf* strbuf, uint32_t start, const char* str) {
assert(start <= strbuf->length);
const char* ptr = strstr(strbuf->chars + start, str);
return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length;
}
[[nodiscard]] static inline uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufPreviousIndexC(const FFstrbuf* strbuf, uint32_t start, char c) {
assert(start <= strbuf->length);
const char* ptr = (const char*) memrchr(strbuf->chars, c, start + 1);
return ptr ? (uint32_t) (ptr - strbuf->chars) : strbuf->length;
}
[[nodiscard]] static inline uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndexC(const FFstrbuf* strbuf, char c) {
return ffStrbufNextIndexC(strbuf, 0, c);
}
[[nodiscard]] static inline uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndex(const FFstrbuf* strbuf, const FFstrbuf* searched) {
return ffStrbufNextIndexS(strbuf, 0, searched->chars);
}
[[nodiscard]] static inline uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline uint32_t ffStrbufFirstIndexS(const FFstrbuf* strbuf, const char* str) {
return ffStrbufNextIndexS(strbuf, 0, str);
}
[[nodiscard]] static inline uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline uint32_t ffStrbufLastIndexC(const FFstrbuf* strbuf, char c) {
if (strbuf->length == 0) {
return 0;
}
@@ -473,19 +500,19 @@ static inline void ffStrbufPrependS(FFstrbuf* strbuf, const char* value) {
return ffStrbufPreviousIndexC(strbuf, strbuf->length - 1, c);
}
static inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1)]] static inline bool ffStrbufSubstrBeforeFirstC(FFstrbuf* strbuf, char c) {
return ffStrbufSubstrBefore(strbuf, ffStrbufFirstIndexC(strbuf, c));
}
static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1)]] static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) {
return ffStrbufSubstrBefore(strbuf, ffStrbufLastIndexC(strbuf, c));
}
[[nodiscard]] static inline bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithC(const FFstrbuf* strbuf, char c) {
return strbuf->chars[0] == c;
}
[[nodiscard]] static inline bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithSN(const FFstrbuf* strbuf, const char* start, uint32_t length) {
if (length > strbuf->length) {
return false;
}
@@ -493,34 +520,34 @@ static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) {
return memcmp(strbuf->chars, start, length) == 0;
}
[[nodiscard]] static inline bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithS(const FFstrbuf* strbuf, const char* start) {
return ffStrbufStartsWithSN(strbuf, start, (uint32_t) strlen(start));
}
[[nodiscard]] static inline bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWith(const FFstrbuf* strbuf, const FFstrbuf* start) {
return ffStrbufStartsWithSN(strbuf, start->chars, start->length);
}
[[nodiscard]] static inline bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start) {
[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t length, const char* start) {
if (length > strbuf->length) {
return false;
}
return strncasecmp(strbuf->chars, start, length) == 0;
}
[[nodiscard]] static inline bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCaseS(const FFstrbuf* strbuf, const char* start) {
return ffStrbufStartsWithIgnCaseNS(strbuf, (uint32_t) strlen(start), start);
}
[[nodiscard]] static inline bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufStartsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* start) {
return ffStrbufStartsWithIgnCaseNS(strbuf, start->length, start->chars);
}
[[nodiscard]] static inline bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithC(const FFstrbuf* strbuf, char c) {
return strbuf->length == 0 ? false : strbuf->chars[strbuf->length - 1] == c;
}
[[nodiscard]] static inline bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) {
[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) {
if (endLength > strbuf->length) {
return false;
}
@@ -528,103 +555,108 @@ static inline bool ffStrbufSubstrBeforeLastC(FFstrbuf* strbuf, char c) {
return memcmp(strbuf->chars + strbuf->length - endLength, end, endLength) == 0;
}
[[nodiscard]] static inline bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithS(const FFstrbuf* strbuf, const char* end) {
return ffStrbufEndsWithNS(strbuf, (uint32_t) strlen(end), end);
}
[[nodiscard]] static inline bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) {
// Not `pure`: the caller-supplied `fn` may have side effects
[[gnu::nonnull(1, 2), nodiscard]] static inline bool ffStrbufEndsWithFn(const FFstrbuf* strbuf, int (*const fn)(int)) {
return strbuf->length == 0 ? false : fn(strbuf->chars[strbuf->length - 1]);
}
[[nodiscard]] static inline bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWith(const FFstrbuf* strbuf, const FFstrbuf* end) {
return ffStrbufEndsWithNS(strbuf, end->length, end->chars);
}
[[nodiscard]] static inline bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) {
[[gnu::nonnull(1, 3), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCaseNS(const FFstrbuf* strbuf, uint32_t endLength, const char* end) {
if (endLength > strbuf->length) {
return false;
}
return strcasecmp(strbuf->chars + strbuf->length - endLength, end) == 0;
}
[[nodiscard]] static inline bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCaseS(const FFstrbuf* strbuf, const char* end) {
return ffStrbufEndsWithIgnCaseNS(strbuf, (uint32_t) strlen(end), end);
}
[[nodiscard]] static inline bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufEndsWithIgnCase(const FFstrbuf* strbuf, const FFstrbuf* end) {
return ffStrbufEndsWithIgnCaseNS(strbuf, end->length, end->chars);
}
static inline void ffStrbufTrim(FFstrbuf* strbuf, char c) {
[[gnu::nonnull(1)]] static inline void ffStrbufTrim(FFstrbuf* strbuf, char c) {
ffStrbufTrimRight(strbuf, c);
ffStrbufTrimLeft(strbuf, c);
}
static inline void ffStrbufTrimSpace(FFstrbuf* strbuf) {
[[gnu::nonnull(1)]] static inline void ffStrbufTrimSpace(FFstrbuf* strbuf) {
ffStrbufTrimRightSpace(strbuf);
ffStrbufTrimLeftSpace(strbuf);
}
static inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedS(const FFstrbuf* strbuf, const char* comp, char separator) {
return ffStrbufMatchSeparatedNS(strbuf, (uint32_t) strlen(comp), comp, separator);
}
static inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparated(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
return ffStrbufMatchSeparatedNS(strbuf, comp->length, comp->chars, separator);
}
static inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) {
return ffStrbufMatchSeparatedIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator);
}
static inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufMatchSeparatedIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
return ffStrbufMatchSeparatedIgnCaseNS(strbuf, comp->length, comp->chars, separator);
}
static inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainS(const FFstrbuf* strbuf, const char* comp, char separator) {
return ffStrbufSeparatedContainNS(strbuf, (uint32_t) strlen(comp), comp, separator);
}
static inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContain(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
return ffStrbufSeparatedContainNS(strbuf, comp->length, comp->chars, separator);
}
static inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainIgnCaseS(const FFstrbuf* strbuf, const char* comp, char separator) {
return ffStrbufSeparatedContainIgnCaseNS(strbuf, (uint32_t) strlen(comp), comp, separator);
}
static inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
[[gnu::nonnull(1, 2), gnu::pure, nodiscard]] static inline bool ffStrbufSeparatedContainIgnCase(const FFstrbuf* strbuf, const FFstrbuf* comp, char separator) {
return ffStrbufSeparatedContainIgnCaseNS(strbuf, comp->length, comp->chars, separator);
}
static inline void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) {
[[gnu::nonnull(1, 2)]] static inline void ffStrbufWriteTo(const FFstrbuf* strbuf, FILE* file) {
fwrite(strbuf->chars, sizeof(*strbuf->chars), strbuf->length, file);
}
static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) {
[[gnu::nonnull(1, 2)]] static inline void ffStrbufPutTo(const FFstrbuf* strbuf, FILE* file) {
ffStrbufWriteTo(strbuf, file);
fputc('\n', file);
}
[[nodiscard]] static inline double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) {
// Not `pure`: `strtod` reads the LC_NUMERIC locale and writes errno
[[gnu::nonnull(1), nodiscard]] static inline double ffStrbufToDouble(const FFstrbuf* strbuf, double defaultValue) {
char* str_end;
double result = strtod(strbuf->chars, &str_end);
return str_end == strbuf->chars ? defaultValue : result;
}
[[nodiscard]] static inline uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) {
// Not `pure`: `strtoull` reads the LC_NUMERIC locale and writes errno
[[gnu::nonnull(1), nodiscard]] static inline uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultValue) {
char* str_end;
unsigned long long result = strtoull(strbuf->chars, &str_end, 10);
return str_end == strbuf->chars ? defaultValue : (uint64_t) result;
}
[[nodiscard]] static inline int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) {
// Not `pure`: `strtoll` reads the LC_NUMERIC locale and writes errno
[[gnu::nonnull(1), nodiscard]] static inline int64_t ffStrbufToSInt(const FFstrbuf* strbuf, int64_t defaultValue) {
char* str_end;
long long result = strtoll(strbuf->chars, &str_end, 10);
return str_end == strbuf->chars ? defaultValue : (int64_t) result;
}
// Returns true if the strbuf is modified
bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf);
[[gnu::nonnull(1)]] [[gnu::nonnull(1)]] 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 }
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
// The NDK marks an API unavailable whenever it is newer than the API level the build targets, and
// fastfetch keeps running on devices below the levels it uses: AImageDecoder is 30, decoding past
// the first frame is 31, and AMediaCodec_getName is 28. CMake compiles the files that reach for
// them with -D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability, which turns
// those entry points into weak references and makes every unguarded use an error. Two things follow:
//
// * A weak reference resolves to null on an older device, so every use needs a run-time check --
// that is FF_ANDROID_API_AT_LEAST. Unguarded, the call would jump to a null pointer.
// * Annotating a helper with FF_ANDROID_REQUIRES_API keeps the check in one place: the compiler rejects
// any call of that helper which is not itself inside a guard.
//
// https://developer.android.com/ndk/guides/using-newer-apis
#define FF_ANDROID_REQUIRES_API(x) [[clang::availability(android, introduced = x)]]
#define FF_ANDROID_API_AT_LEAST(x) __builtin_available(android x, *)
// The UIDs that hold android.permission.DUMP, which is what `dumpsys` checks before it prints
// anything: every other UID is refused on stdout and the command still exits 0, which a caller cannot
// tell apart from a command that printed nothing useful. Forking a `dumpsys` from anywhere else is
// therefore pure waste, and for some fields `dumpsys` is the only route there is.
//
// The refusal is not always the permission, and the text differs per service and per release. On
// an Android 16 device an app UID gets `Permission Denial: can't dump DisplayManagerService ... due to
// missing android.permission.DUMP permission` for `display`, but `Can't find service: battery` for
// `battery` -- that service is not in the list `dumpsys -l` shows an app at all, while `display` is.
// The UID is what decides, so one check covers both.
//
// android.os.Process.ROOT_UID and SHELL_UID. The shell UID has a second use: it owns exactly one
// package name, which the system services accept, so a process that cannot derive its own package
// can answer with that one instead.
typedef enum FFAndroidPrivilegedUid : uint32_t {
FF_ANDROID_PRIVILEGED_UID_ROOT = 0,
FF_ANDROID_PRIVILEGED_UID_SHELL = 2000,
} FFAndroidPrivilegedUid;
// Whether the given UID is allowed to run `dumpsys`. The UID is passed in rather than read here, so
// that the caller can take it from `instance.state.platform.uid` -- which is what makes the
// privileged path reachable from a test that does not run as root or as the shell.
static inline bool ffAndroidIsRootOrShell(uint32_t uid) {
return uid == FF_ANDROID_PRIVILEGED_UID_ROOT || uid == FF_ANDROID_PRIVILEGED_UID_SHELL;
}
+334
View File
@@ -0,0 +1,334 @@
#include "common/android/binder.h"
#include "common/debug.h"
#include "common/io.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
const char* ffBinderOpen(FFBinder* binder) {
*binder = (FFBinder) { .fd = -1, .shared = nullptr, .sharedSize = 0, .protocolVersion = 0 };
FF_AUTO_CLOSE_FD int fd = open(FF_BINDER_DEVICE, O_RDWR | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
return "Failed to open " FF_BINDER_DEVICE;
}
struct binder_version version = { .protocol_version = 0 };
const int versionIoctl = ioctl(fd, BINDER_VERSION, &version);
if (versionIoctl != 0 || version.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
FF_DEBUG("BINDER_VERSION reported protocol %u, expected %u (%s)",
version.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION,
versionIoctl != 0 ? strerror(errno) : "version mismatch");
return "Unsupported binder protocol version";
}
// The region is read-only: the kernel writes reply payloads into it and hands out their
// addresses in BR_REPLY, so it has to stay mapped for as long as the handle is used.
void* shared = mmap(nullptr, FF_BINDER_SHARED_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, 0);
if (shared == MAP_FAILED) {
FF_DEBUG("mmap(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
return "Failed to mmap " FF_BINDER_DEVICE;
}
*binder = (FFBinder) {
.fd = fd,
.shared = (uint8_t*) shared,
.sharedSize = FF_BINDER_SHARED_SIZE,
.protocolVersion = version.protocol_version,
};
fd = -1; // ownership moved into *binder, which ffBinderClose() lets go
return nullptr;
}
void ffBinderClose(FFBinder* binder) {
if (binder->shared != nullptr) {
munmap(binder->shared, binder->sharedSize);
}
if (binder->fd >= 0) {
close(binder->fd);
}
binder->fd = -1;
binder->shared = nullptr;
binder->sharedSize = 0;
}
const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply) {
reply->size = 0;
reply->code = 0;
reply->flags = 0;
reply->handleCount = 0;
reply->fdCount = 0;
if (parcel->truncated) {
FF_DEBUG("The parcel was truncated at %zu bytes, the caller buffer holds %zu",
parcel->size, parcel->capacity);
return "Binder parcel does not fit the caller buffer";
}
uint8_t writeBuffer[sizeof(uint32_t) + sizeof(struct binder_transaction_data)];
const struct binder_transaction_data transaction = {
.target = { .handle = handle },
.cookie = 0,
.code = code,
.flags = flags,
.sender_pid = 0,
.sender_euid = 0,
.data_size = parcel->size,
.offsets_size = 0,
.data = { .ptr = { .buffer = (binder_uintptr_t) (uintptr_t) parcel->data, .offsets = 0 } },
};
const uint32_t transactionCommand = BC_TRANSACTION;
memcpy(writeBuffer, &transactionCommand, sizeof(uint32_t));
memcpy(writeBuffer + sizeof(uint32_t), &transaction, sizeof(transaction));
// The read buffer only has to hold the command words and the binder_transaction_data that
// follows BR_REPLY; the payload itself lives in the mmap region.
uint8_t readBuffer[FF_BINDER_REPLY_BUFFER_SIZE];
struct binder_write_read exchange = {
.write_size = sizeof(writeBuffer),
.write_consumed = 0,
.write_buffer = (binder_uintptr_t) (uintptr_t) writeBuffer,
.read_size = sizeof(readBuffer),
.read_consumed = 0,
.read_buffer = (binder_uintptr_t) (uintptr_t) readBuffer,
};
if (ioctl(binder->fd, BINDER_WRITE_READ, &exchange) != 0) {
FF_DEBUG("BINDER_WRITE_READ(handle=%u, code=%u) failed: %s", handle, code, strerror(errno));
return "BINDER_WRITE_READ failed";
}
const uint8_t* cursor = readBuffer;
const uint8_t* end = readBuffer + (size_t) exchange.read_consumed;
while ((size_t) (end - cursor) >= sizeof(uint32_t)) {
uint32_t command = 0;
memcpy(&command, cursor, sizeof(uint32_t));
cursor += sizeof(uint32_t);
switch (command) {
case BR_NOOP:
case BR_TRANSACTION_COMPLETE:
case BR_SPAWN_LOOPER:
break;
case BR_FAILED_REPLY:
FF_DEBUG("BR_FAILED_REPLY for handle=%u, code=%u", handle, code);
return "Binder transaction failed";
case BR_DEAD_REPLY:
FF_DEBUG("BR_DEAD_REPLY: handle=%u is dead", handle);
return "Binder service is not running";
case BR_INCREFS:
case BR_ACQUIRE:
case BR_RELEASE:
case BR_DECREFS:
cursor += sizeof(struct binder_ptr_cookie);
break;
case BR_DEAD_BINDER:
case BR_CLEAR_DEATH_NOTIFICATION_DONE:
cursor += sizeof(binder_uintptr_t);
break;
case BR_ERROR:
cursor += sizeof(int32_t);
break;
case BR_REPLY: {
if ((size_t) (end - cursor) < sizeof(struct binder_transaction_data)) {
FF_DEBUG("BR_REPLY is truncated: %zu of %zu bytes left",
(size_t) (end - cursor), sizeof(struct binder_transaction_data));
return "Truncated binder reply";
}
struct binder_transaction_data data;
memcpy(&data, cursor, sizeof(data));
cursor += sizeof(data);
reply->code = data.code;
reply->flags = data.flags;
const uint8_t* payload = (const uint8_t*) (uintptr_t) data.data.ptr.buffer;
const size_t payloadSize = (size_t) data.data_size;
const uint8_t* offsets = (const uint8_t*) (uintptr_t) data.data.ptr.offsets;
const size_t offsetsSize = (size_t) data.offsets_size;
const char* error = nullptr;
if (payloadSize > 0) {
if (payloadSize > reply->capacity) {
FF_DEBUG("The reply carries %zu payload bytes, the caller buffer holds %zu",
payloadSize, reply->capacity);
error = "Binder reply does not fit the caller buffer";
} else {
memcpy(reply->data, payload, payloadSize);
reply->size = payloadSize;
}
}
// Walk the offset table for the flat objects the service sent us. A reply may carry
// both kinds at once and they are not interchangeable: a handle is a reference that
// has to be acquired (see below) to outlive the reply buffer, while a file
// descriptor was installed in our own fd table by the kernel and is simply ours.
for (size_t offset = 0; offset + sizeof(binder_size_t) <= offsetsSize; offset += sizeof(binder_size_t)) {
binder_size_t objectOffset = 0;
memcpy(&objectOffset, offsets + offset, sizeof(objectOffset));
if (objectOffset + sizeof(struct flat_binder_object) > payloadSize) {
continue;
}
struct flat_binder_object object;
memcpy(&object, payload + objectOffset, sizeof(object));
if (object.hdr.type == BINDER_TYPE_HANDLE) {
if (reply->handleCount < FF_BINDER_MAX_HANDLES) {
reply->handles[reply->handleCount++] = object.handle;
}
} else if (object.hdr.type == BINDER_TYPE_FD) {
if (reply->fdCount < FF_BINDER_MAX_FDS) {
reply->fds[reply->fdCount++] = (int32_t) object.handle;
} else {
// Nothing is going to hold this one and nothing is going to close it.
close((int) object.handle);
}
}
}
// The kernel gave us a new reference whose only strong reference belongs to the
// reply buffer, so it has to be acquired before that buffer is freed -- otherwise
// the handle silently degrades to a weak reference and every later transaction on
// it fails with BR_FAILED_REPLY. libbinder does the same in acquire_object().
uint8_t tail[FF_BINDER_MAX_HANDLES * 4 * sizeof(uint32_t) + sizeof(uint32_t) + sizeof(binder_uintptr_t)];
size_t tailSize = 0;
for (uint32_t i = 0; i < reply->handleCount; i++) {
const uint32_t acquire = BC_ACQUIRE;
const uint32_t increfs = BC_INCREFS;
memcpy(tail + tailSize, &acquire, sizeof(uint32_t));
tailSize += sizeof(uint32_t);
memcpy(tail + tailSize, &reply->handles[i], sizeof(uint32_t));
tailSize += sizeof(uint32_t);
memcpy(tail + tailSize, &increfs, sizeof(uint32_t));
tailSize += sizeof(uint32_t);
memcpy(tail + tailSize, &reply->handles[i], sizeof(uint32_t));
tailSize += sizeof(uint32_t);
}
const uint32_t freeCommand = BC_FREE_BUFFER;
const binder_uintptr_t freeBuffer = data.data.ptr.buffer;
memcpy(tail + tailSize, &freeCommand, sizeof(uint32_t));
tailSize += sizeof(uint32_t);
memcpy(tail + tailSize, &freeBuffer, sizeof(freeBuffer));
tailSize += sizeof(freeBuffer);
struct binder_write_read release = {
.write_size = tailSize,
.write_consumed = 0,
.write_buffer = (binder_uintptr_t) (uintptr_t) tail,
.read_size = 0,
.read_consumed = 0,
.read_buffer = 0,
};
// This one ioctl carries the whole tail, so its failure is not cosmetic: without
// BC_ACQUIRE the handles handed to the caller are only weakly referenced and every
// later transaction on them fails with BR_FAILED_REPLY, which says nothing about the
// real cause. Reported, but only when nothing more specific is already at hand.
if (ioctl(binder->fd, BINDER_WRITE_READ, &release) != 0 && error == nullptr) {
FF_DEBUG("BC_ACQUIRE/BC_FREE_BUFFER failed: %s", strerror(errno));
error = "Releasing the binder reply failed";
}
return error;
}
default:
FF_DEBUG("Unexpected binder command 0x%08x for handle=%u, code=%u", command, handle, code);
return "Unexpected binder command";
}
}
FF_DEBUG("The binder reply for handle=%u, code=%u held no BR_REPLY (%zu bytes read)",
handle, code, (size_t) exchange.read_consumed);
return "No reply from binder";
}
const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle) {
uint8_t parcelBuffer[256];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
ffBinderParcelPutInterfaceToken(&parcel, FF_BINDER_SM_DESCRIPTOR);
ffBinderParcelPutString16(&parcel, name);
uint8_t replyBuffer[256];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, 0, &parcel, &reply);
if (error != nullptr) {
return error;
}
if (ffBinderReplyIsStatus(&reply)) {
FF_DEBUG("The service manager returned a status parcel for %s, code=%u", name, transactionCode);
return "Service manager rejected the request";
}
const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
if (exception != 0) {
FF_DEBUG("The service manager raised exception %d for %s", exception, name);
return "Service manager raised an exception";
}
if (reply.handleCount == 0) {
FF_DEBUG("The service manager returned no handle for %s", name);
return "Service is not registered";
}
*handle = reply.handles[0];
return nullptr;
}
void ffBinderServiceHandleRelease(FFBinderServiceHandle* service) {
if (service->binder == nullptr || service->handle == 0) {
return;
}
// The mirror of the acquire at the end of ffBinderTransact(): one strong and one weak reference
// each, dropped in the same order they were taken. The kernel keeps the service node alive until
// the last one goes, so this is what stops a short-lived fastfetch from holding one for the whole
// of its life.
const uint32_t commands[] = { BC_RELEASE, BC_DECREFS };
uint8_t tail[2 * 2 * sizeof(uint32_t)];
size_t tailSize = 0;
for (size_t i = 0; i < 2; i++) {
memcpy(tail + tailSize, &commands[i], sizeof(uint32_t));
tailSize += sizeof(uint32_t);
memcpy(tail + tailSize, &service->handle, sizeof(uint32_t));
tailSize += sizeof(uint32_t);
}
struct binder_write_read release = {
.write_size = tailSize,
.write_consumed = 0,
.write_buffer = (binder_uintptr_t) (uintptr_t) tail,
.read_size = 0,
.read_consumed = 0,
.read_buffer = 0,
};
// Not reported: this runs from a cleanup attribute, where there is no caller left to report to,
// and the only way for it to fail is a binder that is already dead -- which every earlier call on
// the same binder would have said already.
ioctl(service->binder->fd, BINDER_WRITE_READ, &release);
service->handle = 0;
}
void ffBinderAcquiredRelease(FFBinderAcquired* acquired) {
if (acquired->replied == nullptr || *acquired->replied == false) {
return; // nothing was acquired
}
// A descriptor is not a reference: the kernel installed it in our own fd table when it delivered
// the reply, and nobody else holds it, so closing it is the whole of giving it back.
for (uint32_t i = 0; i < acquired->fdCount; i++) {
close(acquired->fds[i]);
}
// The mirror of the acquire the transaction appended to its tail, in the same order it took them.
// No binder to talk through means the handles went with it when it was closed.
if (acquired->binder == nullptr || acquired->binder->fd < 0) {
return;
}
for (uint32_t i = 0; i < acquired->handleCount; i++) {
FFBinderServiceHandle handle = { .binder = acquired->binder, .handle = acquired->handles[i] };
ffBinderServiceHandleRelease(&handle);
}
}
+277
View File
@@ -0,0 +1,277 @@
#pragma once
// Android exposes no public C API for the system services that carry the data fastfetch wants: the
// NDK has no BatteryManager / WifiManager, libbinder_ndk only speaks to AIDL HALs (it refuses to
// prepare a transaction for a binder that has no NDK class), and `/system/bin/service call` costs a
// fork/exec worth 15-20 ms. What is left is /dev/binder itself, which is all that `service` uses
// underneath. This header speaks that protocol directly: open() + mmap() + BINDER_WRITE_READ, no
// extra shared library, no child process, ~0.13 ms per call once a handle is cached.
//
// Three details are easy to get wrong. All three were confirmed against the device's own
// libbinder.so and with a ptrace trace of `service`, and getting any of them wrong makes the
// service answer BAD_TYPE (0x80000001) or BR_FAILED_REPLY (0x7211):
//
// * A request parcel must start with three int32 values -- strict mode policy, work source and
// 'SYST'. Parcel::markForBinder() writes nothing for kernel binder, but the receiving side's
// Parcel::enforceInterface() still compares the third word against its own header.
// * Parcel::writeString16() writes the length, the UTF-16 code units, a UTF-16 NUL terminator and
// then pads to four bytes. Skipping the terminator shifts every following field by four bytes.
// * A handle arriving in a reply is owned by that reply buffer. It has to be acquired
// (BC_ACQUIRE + BC_INCREFS) before BC_FREE_BUFFER; otherwise only a weak reference survives and
// every later transaction on it fails, because the kernel looks up a strong reference.
#include "fastfetch.h" // IWYU pragma: keep
// The structures and command words below are not defined here: bionic generates this header from
// bionic/libc/kernel/uapi/linux/android/binder.h and every NDK ships it, so it is the
// authoritative copy of the binder ABI -- struct binder_write_read, struct binder_transaction_data,
// struct flat_binder_object, BINDER_WRITE_READ / BINDER_VERSION, the BC_* and BR_* command words,
// TF_* and BINDER_TYPE_*. Using it beats transcribing it: a second copy can only be wrong, and if
// the header itself is wrong that is the NDK's problem to fix, not something fastfetch could have
// caught by keeping its own.
#include <linux/android/binder.h>
#include <string.h>
#include <sys/ioctl.h>
#define FF_BINDER_DEVICE "/dev/binder"
// The kernel allocates reply payloads out of the region handed to mmap(); 1 MiB is what libbinder
// asks for. Replies themselves are small -- `listServices` on this device is the largest at a few
// hundred bytes -- but the region also carries the per-process buffer pool.
#define FF_BINDER_SHARED_SIZE (1024 * 1024)
// A command word plus the binder_transaction_data that follows it. The payload never lands here:
// it lives in the mmap region, and BR_REPLY only hands over a pointer to it.
#define FF_BINDER_REPLY_BUFFER_SIZE 1024
// How many handles a reply may transfer to us. Replies that carry more than this still succeed, but
// the surplus handles are left unacquired and will fail on first use.
#define FF_BINDER_MAX_HANDLES 4
// How many file descriptors a reply may hand over. A descriptor is not a handle: the kernel installs
// it in the caller's own fd table as it delivers the reply, so it is owned rather than borrowed and
// the caller closes it. A reply carrying more than this still succeeds, and the surplus is closed
// again by ffBinderTransact() because nothing would be left holding it.
#define FF_BINDER_MAX_FDS 4
// The three int32 values Parcel::writeInterfaceToken() puts in front of the descriptor, and that
// Parcel::enforceInterface() checks the last of. These come from libbinder rather than from the
// kernel, which is why they are spelled out here.
#define FF_BINDER_STRICT_MODE_PENALTY_GATHER 0x80000000u // (1 << 31), Parcel.cpp
#define FF_BINDER_UNSET_WORK_SOURCE 0xffffffffu // IPCThreadState::kUnsetWorkSource
#define FF_BINDER_HEADER 0x53595354u // B_PACK_CHARS('S', 'Y', 'S', 'T')
typedef struct FFBinder {
int fd;
uint8_t* shared;
size_t sharedSize;
int32_t protocolVersion;
} FFBinder;
// Opens /dev/binder, checks the protocol version and maps the shared region. Returns nullptr on
// success, a static message otherwise; the device is left closed on failure.
[[gnu::nonnull(1), nodiscard]] const char* ffBinderOpen(FFBinder* binder);
// Usable as a cleanup attribute. Safe on a zeroed or already closed FFBinder.
[[gnu::nonnull(1)]] void ffBinderClose(FFBinder* binder);
// ---------------------------------------------------------------------------------------------
// Parcel writing. The caller owns the buffer; anything that does not fit is dropped and recorded
// in `truncated`, which ffBinderTransact() then reports as an error.
// ---------------------------------------------------------------------------------------------
typedef struct FFBinderParcel {
uint8_t* data;
size_t capacity;
size_t size;
bool truncated;
} FFBinderParcel;
[[nodiscard]] static inline FFBinderParcel ffBinderParcelCreate(uint8_t* data, size_t capacity) {
return (FFBinderParcel) { .data = data, .capacity = capacity, .size = 0, .truncated = false };
}
[[gnu::nonnull(1), nodiscard]] static inline uint8_t* ffBinderParcelReserve(FFBinderParcel* parcel, size_t bytes) {
if (bytes > parcel->capacity - parcel->size) {
parcel->truncated = true;
return nullptr;
}
uint8_t* result = parcel->data + parcel->size;
parcel->size += bytes;
return result;
}
[[gnu::nonnull(1)]] static inline void ffBinderParcelPutU32(FFBinderParcel* parcel, uint32_t value) {
uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(uint32_t));
if (dst != nullptr) {
memcpy(dst, &value, sizeof(uint32_t));
}
}
[[gnu::nonnull(1)]] static inline void ffBinderParcelPutI32(FFBinderParcel* parcel, int32_t value) {
uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(int32_t));
if (dst != nullptr) {
memcpy(dst, &value, sizeof(int32_t));
}
}
[[gnu::nonnull(1)]] static inline void ffBinderParcelPutU64(FFBinderParcel* parcel, uint64_t value) {
uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(uint64_t));
if (dst != nullptr) {
memcpy(dst, &value, sizeof(uint64_t));
}
}
// AIDL string16. The descriptors and service names fastfetch asks for are ASCII, so one byte is one
// UTF-16 code unit; anything else would have to be encoded properly here.
[[gnu::nonnull(1, 2)]] static inline void ffBinderParcelPutString16(FFBinderParcel* parcel, const char* value) {
const size_t length = strlen(value);
const size_t payload = sizeof(uint32_t) + (length + 1) * 2; // length, code units, NUL terminator
const size_t padded = (payload + 3) & ~(size_t) 3;
uint8_t* dst = ffBinderParcelReserve(parcel, padded);
if (dst == nullptr) {
return;
}
memset(dst, 0, padded);
const uint32_t length32 = (uint32_t) length;
memcpy(dst, &length32, sizeof(uint32_t));
for (size_t i = 0; i < length; i++) {
dst[sizeof(uint32_t) + i * 2] = (uint8_t) value[i];
}
// The two bytes after the last code unit stay zero: that is the UTF-16 NUL terminator, and
// Parcel::readInplace() advances by (length + 1) * sizeof(char16_t) to account for it.
}
[[gnu::nonnull(1, 2)]] static inline void ffBinderParcelPutInterfaceToken(FFBinderParcel* parcel, const char* descriptor) {
ffBinderParcelPutU32(parcel, FF_BINDER_STRICT_MODE_PENALTY_GATHER);
ffBinderParcelPutU32(parcel, FF_BINDER_UNSET_WORK_SOURCE);
ffBinderParcelPutU32(parcel, FF_BINDER_HEADER);
ffBinderParcelPutString16(parcel, descriptor);
}
// ---------------------------------------------------------------------------------------------
// Transactions
// ---------------------------------------------------------------------------------------------
typedef struct FFBinderReply {
uint8_t* data; // caller owned; receives the payload
size_t capacity; // ditto
size_t size; // payload length in bytes
uint32_t code; // transaction code the service echoed back
uint32_t flags; // TF_* of the reply
uint32_t handleCount;
uint32_t handles[FF_BINDER_MAX_HANDLES];
uint32_t fdCount;
int32_t fds[FF_BINDER_MAX_FDS]; // owned by the caller, which closes them
} FFBinderReply;
[[nodiscard]] static inline FFBinderReply ffBinderReplyCreate(uint8_t* data, size_t capacity) {
return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {}, .fdCount = 0, .fds = {} };
}
// A reply carrying TF_STATUS_CODE is not an AIDL parcel at all: the first four bytes are the raw
// status_t that onTransact() returned (-74 being UNKNOWN_TRANSACTION, i.e. no such method). Check
// this before interpreting anything else.
[[gnu::nonnull(1), nodiscard]] static inline bool ffBinderReplyIsStatus(const FFBinderReply* reply) {
return (reply->flags & TF_STATUS_CODE) != 0;
}
[[gnu::nonnull(1), nodiscard]] static inline uint32_t ffBinderReadU32(const uint8_t* data, size_t size, size_t offset) {
uint32_t value = 0;
if (offset + sizeof(uint32_t) <= size) {
memcpy(&value, data + offset, sizeof(uint32_t));
}
return value;
}
[[gnu::nonnull(1), nodiscard]] static inline int32_t ffBinderReadI32(const uint8_t* data, size_t size, size_t offset) {
int32_t value = 0;
if (offset + sizeof(int32_t) <= size) {
memcpy(&value, data + offset, sizeof(int32_t));
}
return value;
}
[[gnu::nonnull(1), nodiscard]] static inline uint64_t ffBinderReadU64(const uint8_t* data, size_t size, size_t offset) {
uint64_t value = 0;
if (offset + sizeof(uint64_t) <= size) {
memcpy(&value, data + offset, sizeof(uint64_t));
}
return value;
}
// Synchronous transaction. Returns nullptr on success, a static message otherwise.
//
// `flags` carries the TF_* values for the request, and one of them is not cosmetic: a reply that
// carries a file descriptor is only delivered at all when the request asked with TF_ACCEPT_FDS. A
// request that did not gets BR_FAILED_REPLY instead, which reads like a service that is not running
// rather than like a missing flag. TF_ONE_WAY is the other one the kernel acts on, and it is what
// makes a call fire-and-forget -- there is then no reply to fill `reply` from.
//
// Handles the reply carries are acquired and added to `reply->handles` before the reply buffer is
// freed; descriptors are added to `reply->fds`, which the caller owns and closes.
//
// `handle` 0 is the service manager; every other handle comes from ffBinderLookupService(). A
// service that never replies blocks this call forever -- none of the services fastfetch reads does,
// but there is no timeout here to fall back on.
[[gnu::nonnull(1, 5, 6), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply);
// ---------------------------------------------------------------------------------------------
// Service manager
// ---------------------------------------------------------------------------------------------
#define FF_BINDER_SERVICE_MANAGER_HANDLE 0u
#define FF_BINDER_SM_DESCRIPTOR "android.os.IServiceManager"
// Entry points of android.os.IServiceManager, as this device answers them. The numbers do not match
// the AOSP sources: checkService is 4 here, not 3. FF_BINDER_SM_GET_SERVICE returns a nullable
// IBinder and is the simplest reply to parse; FF_BINDER_SM_CHECK_SERVICE is what `service` itself
// calls and answers with a parcelable instead.
#define FF_BINDER_SM_GET_SERVICE 1u
#define FF_BINDER_SM_GET_SERVICE2 2u
#define FF_BINDER_SM_CHECK_SERVICE 4u
#define FF_BINDER_SM_LIST_SERVICES 6u
// Resolves a service name to a handle through the service manager. Returns nullptr on success.
[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle);
// A handle resolved through the service manager, held together with the binder it came from so that a
// single cleanup attribute can give it back on every path out of the function that took it. Zeroed --
// or left at handle 0 by a lookup that has not run or did not succeed -- it is a no-op, which is what
// makes it safe to declare above the lookup itself.
typedef struct FFBinderServiceHandle {
FFBinder* binder;
uint32_t handle;
} FFBinderServiceHandle;
// Returns the strong and weak references ffBinderLookupService() acquired on the handle. Usable as a
// cleanup attribute; safe on a zeroed or already released FFBinderServiceHandle.
[[gnu::nonnull(1)]] void ffBinderServiceHandleRelease(FFBinderServiceHandle* service);
// ---------------------------------------------------------------------------------------------
// Handles a transaction acquired
// ---------------------------------------------------------------------------------------------
// Whatever ffBinderTransact() collected for the caller, released together on the way out of a
// function -- the handles it acquired, plus the descriptors it delivered, which are owned rather
// than borrowed and would otherwise stay open for the life of the process.
//
// Unlike FFBinderServiceHandle this cannot be declared before the transaction runs, because which
// handles came back is only known afterwards. `replied` is therefore a separate flag the caller
// raises once ffBinderTransact() has returned: it acquires the handles even when it goes on to fail
// (a reply that did not fit the caller buffer still had its BC_ACQUIRE tail sent), so releasing
// without it would drop a reference that was never taken.
typedef struct FFBinderAcquired {
FFBinder* binder;
const uint32_t* handles;
uint32_t handleCount;
const int32_t* fds;
uint32_t fdCount;
const bool* replied;
} FFBinderAcquired;
// Usable as a cleanup attribute; safe on a zeroed FFBinderAcquired, and on one whose transaction did
// not reply.
[[gnu::nonnull(1)]] void ffBinderAcquiredRelease(FFBinderAcquired* acquired);
+754
View File
@@ -0,0 +1,754 @@
#include "common/android/dex.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifdef FF_HAVE_ZLIB
#include "common/library.h"
#include <zlib.h>
#endif
// Offsets into the dex header, from https://source.android.com/docs/core/runtime/dex-format.
#define FF_DEX_ENDIAN_TAG 0x12345678u
#define FF_DEX_HEADER_SIZE 0x70
#define FF_DEX_OFF_FILE_SIZE 0x20
#define FF_DEX_OFF_ENDIAN_TAG 0x28
#define FF_DEX_OFF_STRING_IDS 0x3C
#define FF_DEX_OFF_TYPE_IDS_SIZE 0x40
#define FF_DEX_OFF_TYPE_IDS 0x44
#define FF_DEX_OFF_FIELD_IDS 0x54
#define FF_DEX_OFF_CLASS_DEFS_SIZE 0x60
#define FF_DEX_OFF_CLASS_DEFS 0x64
// class_def_item: [u32 class_idx][u32 access_flags][u32 superclass_idx][u32 interfaces_off]
// [u32 source_file_idx][u32 annotations_off][u32 class_data_off][u32 static_values_off]
#define FF_DEX_CLASS_DEF_SIZE 32
#define FF_DEX_OFF_CLASS_DEF_DATA 24
#define FF_DEX_OFF_CLASS_DEF_STATIC_VALUES 28
// field_id_item: [u16 class_idx][u16 type_idx][u32 name_idx]
#define FF_DEX_FIELD_ID_SIZE 8
#define FF_DEX_OFF_FIELD_ID_NAME 4
// The zip local file header, from APPNOTE.TXT 4.3.7. Only the two length fields are read from it:
// the payload starts right behind them, and the local extra field is allowed to differ from the
// central one, so the offset cannot be computed from the directory alone.
#define FF_ZIP_LOCAL_HEADER_SIZE 30
#define FF_ZIP_OFF_NAME_LENGTH 26
#define FF_ZIP_OFF_EXTRA_LENGTH 28
// The central directory. This is what makes an entry reachable without walking everything in front
// of it, and that matters here: framework.jar is 52 MB and its classes2.dex starts 9.6 MB in, so
// looking for a local header by scanning the file costs a pass over everything before it -- once per
// lookup. Measured on the test device, where the sound module resolves seven codes, that was the
// difference between 30 ms and 2 seconds.
#define FF_ZIP_CENTRAL_MAGIC "PK\x01\x02"
#define FF_ZIP_CENTRAL_SIZE 46
#define FF_ZIP_OFF_CENTRAL_METHOD 10
#define FF_ZIP_OFF_CENTRAL_COMPRESSED_SIZE 20
#define FF_ZIP_OFF_CENTRAL_UNCOMPRESSED_SIZE 24
#define FF_ZIP_OFF_CENTRAL_NAME_LENGTH 28
#define FF_ZIP_OFF_CENTRAL_EXTRA_LENGTH 30
#define FF_ZIP_OFF_CENTRAL_COMMENT_LENGTH 32
#define FF_ZIP_OFF_CENTRAL_LOCAL_OFFSET 42
// The end of central directory record, which points at the directory. It is the last thing in the
// file, behind a comment of at most 64 KiB, so it is searched for backwards over that much.
#define FF_ZIP_EOCD_MAGIC "PK\x05\x06"
#define FF_ZIP_EOCD_SIZE 22
#define FF_ZIP_EOCD_MAX_COMMENT 0xffffu
#define FF_ZIP_OFF_EOCD_DIRECTORY_SIZE 12
#define FF_ZIP_OFF_EOCD_DIRECTORY 16
// A size or offset of 0xFFFFFFFF means the real one is in a zip64 extra field. AOSP's framework jars
// are orders of magnitude below that, and carrying the extra field reader for them is not worth it.
#define FF_ZIP_ZIP64_SENTINEL 0xffffffffu
#define FF_ZIP_METHOD_STORED 0
#define FF_ZIP_METHOD_DEFLATED 8
#define FF_DEX_ENTRY "classes.dex"
// How many `classesN.dex` entries a jar is walked for. A jar that spreads its classes over more
// than this still resolves everything in the entries that were walked; only a class that lives in
// one of the ones past the bound is reported as missing.
#define FF_DEX_MAX_ENTRIES 10
#define FF_DEX_MAGIC "dex\n"
static uint16_t dexU16(const uint8_t* p) {
return (uint16_t) ((uint16_t) p[0] | (uint16_t) ((uint16_t) p[1] << 8));
}
static uint32_t dexU32(const uint8_t* p) {
return (uint32_t) p[0] | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) | ((uint32_t) p[3] << 24);
}
// The dex header points at its tables with offsets that a corrupt or hostile file is free to set
// anywhere, including past the end of the mapping. Testing `end - p` alone does not catch that: once
// `p` is past `end` the subtraction is negative and casting it to size_t turns it into a huge value,
// so the comparison passes and the read goes off the end. Both directions have to be one test.
static bool dexInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, size_t size) {
return p >= dex && p <= end && (size_t) (end - p) >= size;
}
// Same, for a table of `count` elements of `elementSize` bytes. `count` is 64-bit because the
// callers add one to an index that came out of the file, and doing that in 32 bits can wrap to zero
// and make the check pass for an index that is not in the table. Dividing rather than multiplying is
// what keeps a hostile `count` from wrapping the size_t on a 32-bit build -- armeabi-v7a is one, and
// there `count * 4` overflows for any count above 0x3FFFFFFF and the check passes again.
static bool dexTableInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, uint64_t count, size_t elementSize) {
if (p < dex || p > end) {
return false;
}
return (size_t) (end - p) / elementSize >= count;
}
// LEB128, at most five bytes for the 32-bit values a dex stores this way. `end` bounds every read:
// the lengths and indexes decoded here come out of the file, so a truncated one must not walk off
// the end. A read that runs into `end` yields what was decoded so far, and the callers treat the
// resulting value as untrusted -- it is either compared against another count or used as an index
// that is range checked in turn.
static uint32_t dexUleb128(const uint8_t** p, const uint8_t* end) {
uint32_t value = 0;
for (int shift = 0; shift <= 28; shift += 7) {
if (*p >= end) {
break;
}
const uint8_t byte = *(*p)++;
value |= (uint32_t) (byte & 0x7f) << shift;
if ((byte & 0x80) == 0) {
break;
}
}
return value;
}
// The type tag a dex encoded_value carries in the low five bits of its header byte, per the
// `encoded_value` table of the dex format.
//
// VALUE_NULL and VALUE_BOOLEAN are the trap. Their value *is* the header -- the type for a null,
// the size nibble for a boolean -- and they carry **no payload bytes at all**, so the
// `((size - 1) << 5) | type` rule that describes every other tag does not describe them. Taking the
// size nibble at face value and reading one payload byte walks one byte too far, which pairs every
// field after that with the wrong value. It is not a hypothetical: `IAudioService$Stub` declares ten
// `PERMISSIONS_*` arrays that are all null, right behind its `DESCRIPTOR` string and ahead of its
// 324 transaction constants, and those ten spurious bytes made
// `TRANSACTION_getDevicesForAttributesUnprotected` read as 210 instead of 167 -- a code that reaches
// a different method. `IWifiManager$Stub` has none ahead of the field the wifi module reads, which
// is the only reason this went unnoticed.
//
// Naming VALUE_BOOLEAN also matters for a second reason: it is the tag a `static final boolean`
// carries, and treating it as VALUE_INT would hand back a boolean where an int was asked for.
typedef enum FFDexValueType : uint32_t {
FF_DEX_VALUE_INT = 0x04, // sign-extended; one to four payload bytes
FF_DEX_VALUE_NULL = 0x1e, // no payload
FF_DEX_VALUE_BOOLEAN = 0x1f, // no payload; the boolean is in the header's size nibble
} FFDexValueType;
// encoded_value: one header byte holding ((size - 1) << 5) | type, then that many payload bytes.
//
// The `static_values` array is not a list of ints: `IInterface.DESCRIPTOR` is a `static final
// String` and sits in it, and a real Stub class mixes ints, strings, nulls and booleans. So a value
// whose tag is not VALUE_INT is skipped -- its payload is stepped over to keep the walk aligned with
// the field list -- and only `isInt` reports whether the *wanted* field ended up being one. Failing
// on the first non-int instead would abort on `DESCRIPTOR`, before the transaction constants that
// follow it are ever reached. Measured on a device: `IWifiManager$Stub` carries 347 static fields
// whose values are mostly VALUE_INT but include that string, and `getConnectionInfo` is 98 there.
//
// A payload wider than four bytes is not an int either, and the byte count is what keeps a size
// field read out of a corrupt file from shifting past the end. Reading through 64 bits keeps every
// shift in range while counting down; `end` is what keeps a truncated payload from running off the
// end of the mapping.
static bool dexEncodedValue(const uint8_t** p, const uint8_t* end, int32_t* result, bool* isInt) {
if (*p >= end) {
return false;
}
const uint8_t header = *(*p)++;
const uint32_t type = (uint32_t) (header & 0x1f);
// The two tags whose value is the header byte itself. Returning here is what keeps the walk in
// step; `isInt` is false because neither is an int.
if (type == FF_DEX_VALUE_NULL || type == FF_DEX_VALUE_BOOLEAN) {
*result = 0;
*isInt = false;
return true;
}
const uint32_t size = (uint32_t) (header >> 5) + 1;
if (size > 8) {
return false;
}
uint64_t value = 0;
for (uint32_t i = 0; i < size; ++i) {
if (*p >= end) {
return false;
}
if (i < sizeof(value)) {
value |= (uint64_t) (*(*p)++) << (i * 8);
} else {
(void) *(*p)++;
}
}
*isInt = type == FF_DEX_VALUE_INT && size <= sizeof(uint32_t);
if (*isInt) {
// VALUE_INT is sign-extended from its own width, not from 32 bits: a one-byte -1 is written
// as `21 ff`, one byte of 0xff, and has to come back as -1 rather than 255.
const uint32_t bits = size * 8;
if ((value & (1ull << (bits - 1))) != 0) {
value |= ~((1ull << bits) - 1);
}
}
*result = (int32_t) (uint32_t) value;
return true;
}
// string_data_item: a uleb128 length in UTF-16 code units, then MUTF-8 bytes and a NUL terminator.
// The descriptors and field names looked up here are ASCII, where MUTF-8 and UTF-8 agree, so the
// bytes can be used as they are.
static const char* dexString(const uint8_t* dex, const uint8_t* end, uint32_t index) {
const uint8_t* ids = dex + dexU32(dex + FF_DEX_OFF_STRING_IDS);
// `string_ids_size` is not read: the check is that the entry `index` names lies inside the
// mapping, which is what the read below needs, and the table is the only thing that can say it
// without trusting a second field out of the same file.
if (!dexTableInRange(dex, end, ids, (uint64_t) index + 1, 4)) {
return nullptr;
}
const uint8_t* p = dex + dexU32(ids + (size_t) index * 4);
if (p < dex || p >= end) {
return nullptr;
}
(void) dexUleb128(&p, end);
if (p >= end) {
return nullptr;
}
return memchr(p, '\0', (size_t) (end - p)) != nullptr ? (const char*) p : nullptr;
}
// One dex entry, validated. Every offset in the header comes out of the file, so each table is range
// checked before the walk uses it.
typedef struct FFDexTables {
const uint8_t* dex;
const uint8_t* end;
const uint8_t* types;
uint32_t typeCount;
const uint8_t* classes;
uint32_t classCount;
} FFDexTables;
static const char* dexOpen(const uint8_t* dex, size_t size, FFDexTables* tables) {
if (size < FF_DEX_HEADER_SIZE || dexU32(dex + FF_DEX_OFF_ENDIAN_TAG) != FF_DEX_ENDIAN_TAG) {
return "Not a dex file";
}
const uint32_t fileSize = dexU32(dex + FF_DEX_OFF_FILE_SIZE);
if (fileSize > size || fileSize < FF_DEX_HEADER_SIZE) {
return "The dex file size is out of range";
}
const uint8_t* end = dex + fileSize;
const uint8_t* types = dex + dexU32(dex + FF_DEX_OFF_TYPE_IDS);
const uint32_t typeCount = dexU32(dex + FF_DEX_OFF_TYPE_IDS_SIZE);
if (!dexTableInRange(dex, end, types, typeCount, 4)) {
return "The dex type table is out of range";
}
const uint8_t* classes = dex + dexU32(dex + FF_DEX_OFF_CLASS_DEFS);
const uint32_t classCount = dexU32(dex + FF_DEX_OFF_CLASS_DEFS_SIZE);
if (!dexTableInRange(dex, end, classes, classCount, FF_DEX_CLASS_DEF_SIZE)) {
return "The dex class table is out of range";
}
tables->dex = dex;
tables->end = end;
tables->types = types;
tables->typeCount = typeCount;
tables->classes = classes;
tables->classCount = classCount;
return nullptr;
}
// One pass over the type table, resolving the type index of every class that still has a request
// open. This pass is what a lookup costs: `framework.jar`'s classes.dex lists 8428 descriptors, each
// one a `string_data_item` read from a different part of a 51 MB mapping. Matching every request
// against the same pass is what makes a batch cheaper than the sum of its fields -- the descriptor is
// read once however many classes are being looked for, and the pass ends as soon as the last request
// has an index rather than at the end of the table.
//
// `typeIndexes` is parallel to `requests` and rebuilt here rather than kept across entries: a type
// index is a property of the entry's own table, and the next entry numbers its types differently.
static void dexFindTypes(const FFDexTables* tables, const FFDexStaticIntRequest* requests, const uint8_t* settled, uint32_t* typeIndexes, uint32_t count) {
uint32_t remaining = 0;
for (uint32_t r = 0; r < count; ++r) {
typeIndexes[r] = UINT32_MAX;
if (!settled[r]) {
++remaining;
}
}
for (uint32_t i = 0; i < tables->typeCount && remaining > 0; ++i) {
const char* descriptor = dexString(tables->dex, tables->end, dexU32(tables->types + (size_t) i * 4));
if (descriptor == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != UINT32_MAX || strcmp(descriptor, requests[r].classDescriptor) != 0) {
continue;
}
typeIndexes[r] = i;
--remaining;
}
}
}
// Walks the static field list of the class that `typeIndex` names and the class's static value array
// in step, writing the value of every request for that class whose field name matches. Both lists are
// ordered by field index and cover exactly the same fields -- when they do not, the pairing is not
// trustworthy and nothing is written.
//
// `defined` says whether this dex *defines* the class, which is not the same as mentioning it: a dex
// lists every type it references in its type table, so a class that a later entry of the jar defines
// still has a type index here, with no class_def_item behind it. Only a class_def_item makes the
// answer this dex's; without one the caller moves on to the next entry.
//
// A request this class cannot answer -- a field it does not declare, or one it declares as something
// other than an int -- is closed here rather than reported: the class is this dex's, so no later
// entry of the jar could give it a different answer, and one field the caller can live without must
// not hold back the rest of the batch. It is closed with the sentinel, and what this returns is only
// ever a dex that cannot be read, which is the one thing no later entry can fix.
static const char* dexClassStaticInts(const FFDexTables* tables, uint32_t typeIndex, const FFDexStaticIntRequest* requests, const uint32_t* typeIndexes, uint8_t* settled, uint32_t count, bool* defined) {
*defined = false;
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
for (uint32_t i = 0; i < tables->classCount; ++i) {
const uint8_t* classDef = tables->classes + (size_t) i * FF_DEX_CLASS_DEF_SIZE;
if (dexU32(classDef) != typeIndex) {
continue;
}
// Defined here, so from this point on the answer is this dex's, whatever it turns out to be.
*defined = true;
const uint8_t* fields = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_DATA);
const uint8_t* values = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_STATIC_VALUES);
// One byte each, so that the first uleb128 below has something to read.
if (!dexInRange(dex, end, fields, 1) || !dexInRange(dex, end, values, 1)) {
return "The dex class data is out of range";
}
const uint32_t staticFields = dexUleb128(&fields, end);
(void) dexUleb128(&fields, end); // instance_fields_size
(void) dexUleb128(&fields, end); // direct_methods_size
(void) dexUleb128(&fields, end); // virtual_methods_size
const uint32_t staticValues = dexUleb128(&values, end);
if (staticFields != staticValues) {
return "The dex static fields and values do not pair up";
}
const uint8_t* fieldIds = dex + dexU32(dex + FF_DEX_OFF_FIELD_IDS);
uint32_t fieldIndex = 0;
for (uint32_t j = 0; j < staticFields; ++j) {
// The two arrays are walked in step, so running out of either one is a truncation.
if (fields >= end || values >= end) {
return "The dex static field list is truncated";
}
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
return "The dex field index is out of range";
}
fieldIndex += fieldIndexDiff;
(void) dexUleb128(&fields, end); // access_flags
int32_t value;
bool isInt;
if (!dexEncodedValue(&values, end, &value, &isInt)) {
return "The dex static value list is truncated";
}
if (!dexTableInRange(dex, end, fieldIds, (uint64_t) fieldIndex + 1, FF_DEX_FIELD_ID_SIZE)) {
return "The dex field table is out of range";
}
const char* name = dexString(dex, end, dexU32(fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
if (name == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != typeIndex || strcmp(name, requests[r].fieldName) != 0) {
continue;
}
if (!isInt) {
// The field exists but is not an int, so its value is not the constant asked
// for. Closed the way a field the class does not declare is, because it is this
// dex's answer too. The check is here rather than on the first non-int in the
// list because the list legitimately holds strings (`DESCRIPTOR`) and booleans.
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
settled[r] = 1;
continue;
}
*requests[r].result = value;
settled[r] = 1;
}
}
// The class is defined here, so a request of its own that went unanswered is one whose field
// the class does not declare as a static field. Closed here for the same reason as above.
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r] && typeIndexes[r] == typeIndex) {
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
settled[r] = 1;
}
}
return nullptr;
}
// The descriptor is in this dex's type table but the class itself is defined by another entry of
// the jar. That is not a failure: `defined` stays false and the caller reads the next entry.
return nullptr;
}
// ---------------------------------------------------------------------------------------------
// Locating classes.dex
// ---------------------------------------------------------------------------------------------
typedef struct FFDexMapping {
uint8_t* mapped; // the jar, as mapped; nullptr once released
size_t mappedSize;
const uint8_t* data; // the dex bytes, inside `mapped`
size_t size;
} FFDexMapping;
static void wrapDexMapping(FFDexMapping* mapping) {
assert(mapping);
if (mapping->mapped != nullptr) {
munmap(mapping->mapped, mapping->mappedSize);
}
}
// Locates an entry through the jar's central directory.
//
// The sizes and the compression method are read from the directory rather than from the entry's own
// local header, which is the more reliable of the two: a local header is allowed to leave its sizes
// at zero and put them in a data descriptor behind the payload, and an archive writer is free to
// choose that. Only the local header's two length fields are used, because they are what says where
// the payload begins -- the local extra field does not have to match the central one.
static const char* findDexEntry(const uint8_t* jar, size_t jarSize, const char* entry, const uint8_t** data, size_t* dataSize, uint32_t* uncompressedSize, uint16_t* method) {
const size_t entryLength = strlen(entry);
if (jarSize < FF_ZIP_EOCD_SIZE) {
return "The jar is too small to be a zip";
}
// The record is the last thing in the file, so it is searched for backwards -- over at most the
// 64 KiB a trailing comment can take, not over the whole archive.
const size_t newest = jarSize - FF_ZIP_EOCD_SIZE;
const size_t oldest = jarSize > FF_ZIP_EOCD_MAX_COMMENT + FF_ZIP_EOCD_SIZE
? jarSize - FF_ZIP_EOCD_MAX_COMMENT - FF_ZIP_EOCD_SIZE
: 0;
size_t eocd = SIZE_MAX;
for (size_t i = newest;; --i) {
if (memcmp(jar + i, FF_ZIP_EOCD_MAGIC, sizeof(FF_ZIP_EOCD_MAGIC) - 1) == 0) {
eocd = i;
break;
}
if (i == oldest) {
// The walk stops here rather than at zero: a `size_t` countdown that has to reach zero
// before it stops would wrap and read the whole file backwards, which is the very scan
// this is here to avoid.
break;
}
}
if (eocd == SIZE_MAX) {
FF_DEBUG("No end-of-central-directory record in the last %zu bytes of a %zu byte jar",
newest - oldest + FF_ZIP_EOCD_SIZE, jarSize);
return "The jar has no zip directory";
}
const uint32_t directorySize = dexU32(jar + eocd + FF_ZIP_OFF_EOCD_DIRECTORY_SIZE);
const uint32_t directoryOffset = dexU32(jar + eocd + FF_ZIP_OFF_EOCD_DIRECTORY);
if (directoryOffset > jarSize || directorySize > jarSize - directoryOffset) {
return "The zip directory is out of range";
}
const size_t directoryEnd = (size_t) directoryOffset + directorySize;
for (size_t offset = directoryOffset; offset + FF_ZIP_CENTRAL_SIZE <= directoryEnd;) {
if (memcmp(jar + offset, FF_ZIP_CENTRAL_MAGIC, sizeof(FF_ZIP_CENTRAL_MAGIC) - 1) != 0) {
return "A zip directory entry is malformed";
}
const uint16_t nameLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_NAME_LENGTH);
const uint16_t extraLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_EXTRA_LENGTH);
const uint16_t commentLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_COMMENT_LENGTH);
const size_t entrySize = FF_ZIP_CENTRAL_SIZE + (size_t) nameLength + (size_t) extraLength + (size_t) commentLength;
if (entrySize > directoryEnd - offset) {
FF_DEBUG("A zip entry claims %zu bytes but only %zu remain in the directory",
entrySize, (size_t) (directoryEnd - offset));
return "A zip directory entry runs past the directory";
}
if (nameLength == entryLength && memcmp(jar + offset + FF_ZIP_CENTRAL_SIZE, entry, entryLength) == 0) {
const uint32_t compressedSize = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_COMPRESSED_SIZE);
const uint32_t uncompressed = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_UNCOMPRESSED_SIZE);
const uint32_t localOffset = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_LOCAL_OFFSET);
if (compressedSize == FF_ZIP_ZIP64_SENTINEL || uncompressed == FF_ZIP_ZIP64_SENTINEL || localOffset == FF_ZIP_ZIP64_SENTINEL) {
FF_DEBUG("The dex entry is zip64: compressed=%u, uncompressed=%u, localOffset=%u",
compressedSize, uncompressed, localOffset);
return "A dex entry is stored as zip64, which is not supported";
}
if (localOffset > jarSize || FF_ZIP_LOCAL_HEADER_SIZE > jarSize - localOffset) {
return "A zip local header is out of range";
}
const uint16_t localNameLength = dexU16(jar + localOffset + FF_ZIP_OFF_NAME_LENGTH);
const uint16_t localExtraLength = dexU16(jar + localOffset + FF_ZIP_OFF_EXTRA_LENGTH);
const size_t headerSize = FF_ZIP_LOCAL_HEADER_SIZE + (size_t) localNameLength + (size_t) localExtraLength;
if (headerSize > jarSize - localOffset || compressedSize > jarSize - localOffset - headerSize) {
return "The zip entry bounds are out of range";
}
*method = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_METHOD);
*uncompressedSize = uncompressed;
*data = jar + localOffset + headerSize;
*dataSize = compressedSize;
return nullptr;
}
offset += entrySize;
}
FF_DEBUG("No entry in the %zu byte jar is named %s", jarSize, entry);
return "The jar has no usable dex entry";
}
// Fallback for a jar whose entry is STORED but whose header has no sizes: the dex magic is then a
// literal run of bytes in the file, and the header that follows it validates or it does not. It
// only ever locates the *first* dex in the jar, so it is a fallback for `classes.dex` alone.
static const uint8_t* findDexMagic(const uint8_t* jar, size_t jarSize, size_t* dexSize) {
for (size_t i = 0; i + FF_DEX_HEADER_SIZE <= jarSize; ++i) {
if (memcmp(jar + i, FF_DEX_MAGIC, sizeof(FF_DEX_MAGIC) - 1) != 0) {
continue;
}
const uint8_t* dex = jar + i;
const uint32_t fileSize = dexU32(dex + FF_DEX_OFF_FILE_SIZE);
if (dexU32(dex + FF_DEX_OFF_ENDIAN_TAG) != FF_DEX_ENDIAN_TAG) {
continue;
}
if (fileSize < FF_DEX_HEADER_SIZE || fileSize > jarSize - i) {
continue;
}
*dexSize = fileSize;
return dex;
}
return nullptr;
}
#ifdef FF_HAVE_ZLIB
static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t uncompressedSize, uint8_t** out) {
FF_LIBRARY_LOAD(zlib, "dlopen(libz) failed", "libz" FF_LIBRARY_EXTENSION, 2)
FF_LIBRARY_LOAD_SYMBOL(zlib, inflateInit2_, "dlsym(inflateInit2_) failed")
FF_LIBRARY_LOAD_SYMBOL(zlib, inflate, "dlsym(inflate) failed")
FF_LIBRARY_LOAD_SYMBOL(zlib, inflateEnd, "dlsym(inflateEnd) failed")
// Released by the cleanup on every path out below; ownership moves to the caller by clearing
// the pointer, which is also why no failure path has to free it by hand.
FF_AUTO_FREE uint8_t* buffer = malloc(uncompressedSize);
if (buffer == nullptr) {
FF_DEBUG("malloc(%u) for the inflated dex failed", uncompressedSize);
return "malloc failed";
}
// `uncompress` is not usable here: a zip entry holds a raw deflate stream, without the two byte
// zlib header that entry point insists on. A negative window size tells inflate to skip the
// header, which is what the zip format expects.
z_stream stream = {};
stream.next_in = (Bytef*) data;
stream.avail_in = (uInt) dataSize;
stream.next_out = buffer;
stream.avail_out = (uInt) uncompressedSize;
const int initStatus = ffinflateInit2_(&stream, -MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream));
if (initStatus != Z_OK) {
FF_DEBUG("inflateInit2() failed: zlib status %d", initStatus);
return "inflateInit2 failed";
}
const int status = ffinflate(&stream, Z_FINISH);
ffinflateEnd(&stream);
if (status != Z_STREAM_END || stream.total_out != (uLong) uncompressedSize) {
FF_DEBUG("inflate() returned %d, produced %lu of %u bytes",
status, stream.total_out, uncompressedSize);
return "Inflating the dex failed";
}
*out = buffer;
buffer = nullptr; // the caller owns it from here
return nullptr;
}
#endif
const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* requests, uint32_t count) {
// Every request is written the sentinel before anything can fail, so that a caller reads a result
// rather than whatever its own stack held there, however this returns. Nothing below has to
// remember to do it on its way out.
for (uint32_t r = 0; r < count; ++r) {
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
}
if (count == 0) {
return nullptr;
}
[[gnu::cleanup(wrapDexMapping)]] FFDexMapping mapping = {};
{
FF_AUTO_CLOSE_FD const int fd = open(jarPath, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(%s) failed: %s", jarPath, strerror(errno));
return "open(jar) failed";
}
struct stat st = {};
const int statStatus = fstat(fd, &st);
if (statStatus != 0 || st.st_size <= 0) {
FF_DEBUG("fstat(%s) reported %s (%lld bytes)", jarPath,
statStatus != 0 ? strerror(errno) : "an empty file", (long long) st.st_size);
return "fstat(jar) failed";
}
mapping.mappedSize = (size_t) st.st_size;
mapping.mapped = mmap(nullptr, mapping.mappedSize, PROT_READ, MAP_PRIVATE, fd, 0);
// The mapping outlives the descriptor, so the descriptor can go either way from here.
if (mapping.mapped == MAP_FAILED) {
FF_DEBUG("mmap(%s, %zu bytes) failed: %s", jarPath, mapping.mappedSize, strerror(errno));
mapping.mapped = nullptr;
return "mmap(jar) failed";
}
}
// One byte per request saying whether it is closed -- answered, or given up on -- and the type
// index it was found under in the entry being walked. Both are sized from `count` rather than
// from a fixed bound, and both are released by their cleanup however this function returns.
FF_AUTO_FREE uint8_t* settled = calloc(count, 1);
FF_AUTO_FREE uint32_t* typeIndexes = malloc((size_t) count * sizeof(uint32_t));
if (settled == nullptr || typeIndexes == nullptr) {
FF_DEBUG("Allocating %zu bytes for %u static int requests failed",
(size_t) count * (1 + sizeof(uint32_t)), count);
return "malloc failed";
}
// A jar spreads its classes over `classes.dex`, `classes2.dex`, `classes3.dex`, ... and the
// class being looked for is in whichever one the build put it in: `IAudioService$Stub` is in the
// second dex of framework.jar, while `IWifiManager$Stub` is in the first of framework-wifi.jar.
// The entries are therefore tried in order, and the first one that defines the class answers.
// The count is a bound on the walk, not a claim about the jar -- AOSP has never shipped more
// than a handful, and a class that is in none of them is left unanswered either way.
//
// The walk ends as soon as the last request is closed, so an entry that holds every class asked
// for -- which is what the modules here ask for, one class per interface -- costs one entry read
// and one pass over its type table.
uint32_t pending = count;
const char* error = nullptr;
for (uint32_t index = 1; index <= FF_DEX_MAX_ENTRIES && pending > 0; ++index) {
char entry[sizeof("classes999.dex")];
if (index == 1) {
memcpy(entry, FF_DEX_ENTRY, sizeof(FF_DEX_ENTRY));
} else {
(void) snprintf(entry, sizeof(entry), "classes%u.dex", index);
}
const uint8_t* data = nullptr;
size_t dataSize = 0;
uint32_t uncompressedSize = 0;
uint16_t method = FF_ZIP_METHOD_STORED;
// The decompressed dex, when the entry needed one. The cleanup releases it at the end of
// the iteration, so every entry gets a buffer of its own and no way out of the loop -- an
// early return included -- can leak one.
FF_AUTO_FREE uint8_t* inflated = nullptr;
if (findDexEntry(mapping.mapped, mapping.mappedSize, entry, &data, &dataSize, &uncompressedSize, &method) != nullptr) {
if (index > 1) {
// The magic scan only ever finds the first dex, so it cannot stand in for a later
// entry: running past the end of the entries that exist ends the walk.
break;
}
data = findDexMagic(mapping.mapped, mapping.mappedSize, &dataSize);
if (data == nullptr) {
FF_DEBUG("Neither %s nor a dex magic signature is in the %zu byte jar", entry, mapping.mappedSize);
return "No dex in the jar";
}
} else if (method == FF_ZIP_METHOD_DEFLATED) {
#ifdef FF_HAVE_ZLIB
error = inflateDex(data, dataSize, uncompressedSize, &inflated);
if (error != nullptr) {
return error;
}
data = inflated;
dataSize = uncompressedSize;
#else
return "The jar deflates its dex entries and fastfetch was built without zlib";
#endif
} else if (method != FF_ZIP_METHOD_STORED) {
FF_DEBUG("%s uses compression method %u, only %u (stored) and %u (deflated) are handled",
entry, method, FF_ZIP_METHOD_STORED, FF_ZIP_METHOD_DEFLATED);
return "A dex entry uses an unsupported compression method";
}
// A STORED entry needs no further work: `dataSize` already holds the `compressedSize` that
// `findDexEntry` checked against the mapping, and for a STORED entry the payload is the file
// itself. The header's `uncompressedSize` is deliberately not used for it -- nothing bounds
// that value, so a corrupt one reaches past the end of the mapping, which is exactly what the
// dex header would then be validated against.
mapping.data = data;
mapping.size = dataSize;
FFDexTables tables = {};
error = dexOpen(mapping.data, mapping.size, &tables);
if (error != nullptr) {
// The dex cannot be read at all, which no later entry can fix.
return error;
}
dexFindTypes(&tables, requests, settled, typeIndexes, count);
// Every class this entry defines is walked once, however many of its fields were asked for.
// A request whose class is not in this entry's type table keeps `UINT32_MAX` and is left for
// the next entry; one whose class is only referenced here is left the same way by the walk
// below, which is what `defined` reports.
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] == UINT32_MAX) {
continue;
}
const uint32_t typeIndex = typeIndexes[r];
bool defined = false;
error = dexClassStaticInts(&tables, typeIndex, requests, typeIndexes, settled, count, &defined);
if (error != nullptr) {
// The dex cannot be read, which no later entry can fix.
return error;
}
if (!defined) {
// Only referenced here, so the class is left for the next entry -- and the requests
// that share it are marked as walked, since one lookup of a class answers all of
// them and the class table should not be scanned once per field.
for (uint32_t s = r; s < count; ++s) {
if (typeIndexes[s] == typeIndex) {
typeIndexes[s] = UINT32_MAX;
}
}
}
}
pending = 0;
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r]) {
++pending;
}
}
}
// What is still open is a class no entry of the jar defines. That is an answer like any other --
// the results already hold the sentinel, written before the walk began -- so the only thing left
// is to say so for a debug build, where the sentinel on its own does not tell "this build has no
// such method" apart from "the reader went wrong".
for (uint32_t r = 0; r < count; ++r) {
if (*requests[r].result == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("\"%s\" is not a static int field of \"%s\"", requests[r].fieldName, requests[r].classDescriptor);
}
}
return nullptr;
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
// AIDL gives every method of an interface a transaction code at build time:
//
// static final int TRANSACTION_<method> = IBinder.FIRST_CALL_TRANSACTION + <declaration index>;
//
// so the number is a property of the `.aidl` of the release that built the device's jar, not of any
// public header. Counting the methods in an AOSP checkout only describes that checkout, and it goes
// wrong the moment a release or a vendor fork inserts a method ahead of the one being looked for:
// `IWifiManager.getConnectionInfo` is 29 on Android 11 and 41 on Android 16. There is no negotiation
// and no discovery -- a wrong code reaches a different method, or none.
//
// Being a compile-time constant, the value lands in the `static_values` array of the `X$Stub` class
// in the jar's dex, paired position by position with that class's static field list. Reading the two
// together gives the mapping of the build that is actually on the device, on any release, with
// nothing to keep in sync.
//
// `libdexfile.so` cannot do this for us. It is dlopen-able from an app UID through the ART apex, but
// it is not in the NDK and its only C ABI -- the `ADexFile_*` family -- covers methods, not fields;
// the field and class-data APIs are C++ with no ABI guarantee.
//
// `ffReadFileBuffer` would work but pulls the whole jar through the heap for one integer. The jar is
// mapped instead, and only the pages actually read are faulted in.
#include "fastfetch.h"
// What a request is written when the jar does not answer it, on every path out of ffDexStaticInts --
// a jar that cannot be opened and a field the class does not declare both leave it behind, so a
// caller reads a result rather than what its own stack happened to hold there. No transaction code
// is negative: AIDL numbers them from `IBinder.FIRST_CALL_TRANSACTION`, which is 1.
//
// This is what keeps the reader a lookup and nothing more. Whether a code is one the caller cannot do
// without or one it only uses when the build declares it, whether what came back is enough to carry
// on, and whether the module reports a value or an error are the caller's decisions, made by reading
// the results. The reader finds numbers and says which ones it could not.
#define FF_DEX_STATIC_INT_UNRESOLVED (-1)
// One `<classDescriptor>.<fieldName>` lookup, and where its value is written. `classDescriptor` is
// the dex type descriptor, e.g. "Landroid/net/wifi/IWifiManager$Stub;".
typedef struct FFDexStaticIntRequest {
const char* classDescriptor;
const char* fieldName;
int32_t* result;
} FFDexStaticIntRequest;
// Resolves the int value of every request out of the dex entries of `jarPath`, walking `classes.dex`,
// `classes2.dex`, ... in order until one of them defines the class.
//
// Asking for several fields at once is not a convenience. A lookup is dominated by the type table,
// which lists every type the dex references -- 8428 descriptors in `framework.jar`'s `classes.dex`,
// each one read from a different part of a 51 MB mapping -- and a single pass answers every request
// whose class is named in it. Measured over three devices' own `framework.jar`, four fields of two
// classes cost one pass rather than four.
//
// Every request is written a code or FF_DEX_STATIC_INT_UNRESOLVED. A request is never allowed to
// take the others down with it: the classes a caller asks for are looked for independently, and one
// the jar does not declare leaves its own result at the sentinel while the rest are answered.
//
// Not finding a code is not a failure. A jar that is readable answers the call, whether it declares
// every field asked for, some of them, or none -- the results say which, and what that means for the
// module is the caller's to decide. The return value is therefore reserved for the one thing the
// caller cannot read out of the results: the jar itself, which is reported when it cannot be opened
// or does not hold a dex that can be read. nullptr means the lookup ran.
[[gnu::nonnull(1, 2), nodiscard]] const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* requests, uint32_t count);
+65
View File
@@ -0,0 +1,65 @@
#include "common/android/package.h"
#include "common/android/api.h"
#include "common/debug.h"
#include "common/strutil.h"
#include <string.h>
#include <unistd.h>
// There is no way for a process to ask for its own package name: it is not in /proc/self/status, and
// an app cannot list /data/data. The executable path carries it instead -- an app's binaries live
// under /data/data/<package>/ or /data/user/<user>/<package>/, and /proc/self/exe resolves there.
// Under Termux that is /data/data/com.termux/files/usr/bin/<binary>, whose third component is the
// package.
//
// The memory cgroup carries the same information (4:memory:/apps/com.termux on the device this was
// verified on), but the unified hierarchy spells it out as /apps/uid_10478/pid_26943 with no name
// left in it, so the executable is the portable half of the two.
bool ffAndroidGetOwnPackage(char* buffer, size_t capacity) {
char path[4096];
const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (length <= 0) {
FF_DEBUG("Cannot read /proc/self/exe, so the calling package cannot be derived");
return false;
}
path[length] = '\0';
const char* rest = nullptr;
const char* const dataPrefix = "/data/data/";
const char* const userPrefix = "/data/user/";
if (ffStrStartsWith(path, dataPrefix)) {
rest = path + strlen(dataPrefix);
} else if (ffStrStartsWith(path, userPrefix)) {
// /data/user/<user>/<package>/... -- the user number has to be stepped over before the
// package name starts, and only strchr() answers where that is.
rest = strchr(path + strlen(userPrefix), '/');
if (rest != nullptr) {
rest += 1;
}
}
if (rest == nullptr) {
const uint32_t uid = instance.state.platform.uid;
FF_DEBUG("The executable is not in an app data directory (\"%s\"), uid %u", path, uid);
if (uid != FF_ANDROID_PRIVILEGED_UID_SHELL) {
return false;
}
static const char shellPackage[] = FF_ANDROID_SHELL_PACKAGE;
if (sizeof(shellPackage) > capacity) {
return false;
}
memcpy(buffer, shellPackage, sizeof(shellPackage));
FF_DEBUG("The calling package is \"%s\" (the shell owns it)", buffer);
return true;
}
const char* end = strchr(rest, '/');
const size_t nameLength = end != nullptr ? (size_t) (end - rest) : strlen(rest);
if (nameLength == 0 || nameLength >= capacity) {
return false;
}
memcpy(buffer, rest, nameLength);
buffer[nameLength] = '\0';
FF_DEBUG("The calling package is \"%s\"", buffer);
return true;
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "fastfetch.h"
// The one package name the shell UID owns. android.os.Process.SHELL_UID is a single app UID that
// owns exactly one package, so a process that cannot derive a package of its own -- a static build
// pushed to /data/local/tmp is the case that showed up -- can answer with this name and have the
// system services accept it. See FF_ANDROID_PRIVILEGED_UID_SHELL in common/android/api.h.
#define FF_ANDROID_SHELL_PACKAGE "com.android.shell"
// Some of the system services take the caller's package name as an argument and check it against the
// calling UID rather than against the name being real: a name nobody owns, and a real name belonging
// to another app, are refused exactly like a null one. So it has to be the caller's own name.
//
// Whether the name is checked at all depends on the service and the release -- `getConnectionInfo`
// answered "Package com.termux does not belong to <shell uid>" on one Android 16 device while the
// same name was accepted on an Android 11 one, and `getWallpaper` refuses a null, empty or foreign
// name on that Android 16 device -- so passing the real one is what works everywhere.
//
// Writes the name into `buffer`, terminator included, and returns whether one was found. `capacity`
// is the caller's, because the parcel the name ends up in is sized from it.
[[gnu::nonnull(1), nodiscard]] bool ffAndroidGetOwnPackage(char* buffer, size_t capacity);
+32
View File
@@ -1,36 +1,47 @@
#include "cf_helpers.h"
#include "common/debug.h"
const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result) {
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberSInt64Type, result)) {
FF_DEBUG("CFNumberGetValue(kCFNumberSInt64Type) failed, the number is of type 0x%08lx",
(unsigned long) CFNumberGetType((CFNumberRef) cf));
return "Number type is not SInt64";
}
return nullptr;
} else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
if (CFDataGetLength((CFDataRef) cf) != sizeof(int64_t)) {
FF_DEBUG("CFData holds %ld bytes, int64_t needs %zu",
(long) CFDataGetLength((CFDataRef) cf), sizeof(int64_t));
return "Data length is not sizeof(int64_t)";
}
CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(int64_t)), (uint8_t*) result);
return nullptr;
}
FF_DEBUG("Expected a CFNumber or CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFNumber' nor 'CFData'";
}
const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) {
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberSInt32Type, result)) {
FF_DEBUG("CFNumberGetValue(kCFNumberSInt32Type) failed, the number is of type 0x%08lx",
(unsigned long) CFNumberGetType((CFNumberRef) cf));
return "Number type is not SInt32";
}
return nullptr;
} else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
if (CFDataGetLength((CFDataRef) cf) != sizeof(*result)) {
FF_DEBUG("CFData holds %ld bytes, int32_t needs %zu",
(long) CFDataGetLength((CFDataRef) cf), sizeof(*result));
return "Data length is not sizeof(int32_t)";
}
CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(*result)), (uint8_t*) result);
return nullptr;
}
FF_DEBUG("Expected a CFNumber or CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFNumber' nor 'CFData'";
}
@@ -38,16 +49,20 @@ const char* ffCfNumGetDouble(CFTypeRef cf, double* result) {
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberDoubleType, result) &&
!CFNumberGetValue((CFNumberRef) cf, kCFNumberFloatType, result)) {
FF_DEBUG("CFNumber is of type 0x%08lx, neither Double nor Float",
(unsigned long) CFNumberGetType((CFNumberRef) cf));
return "Number type is not Double or Float";
}
return nullptr;
}
FF_DEBUG("Expected a CFNumber, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFNumber'";
}
const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result) {
if (CFGetTypeID(cf) != CFDateGetTypeID()) {
FF_DEBUG("Expected a CFDate, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFDate'";
}
@@ -76,6 +91,7 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) {
}
ffStrbufEnsureFixedLengthFree(result, (uint32_t) CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8));
if (!CFStringGetCString(cfStr, result->chars, result->allocated, kCFStringEncodingUTF8)) {
FF_DEBUG("CFStringGetCString() failed for a %u character string", (unsigned) length);
return "CFStringGetCString() failed";
}
// CFStringGetCString ensures the buffer is NUL terminated
@@ -93,6 +109,7 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) {
result->length = (uint32_t) strnlen(result->chars, length);
result->chars[result->length] = '\0';
} else {
FF_DEBUG("Expected a CFString or CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFString' nor 'CFData'";
}
@@ -116,6 +133,7 @@ const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) {
result->length = length;
result->chars[result->length] = '\0';
} else {
FF_DEBUG("Expected a CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFData'";
}
@@ -125,6 +143,7 @@ const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) {
const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -134,6 +153,7 @@ const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* r
const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -143,10 +163,12 @@ const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstr
const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) {
CFBooleanRef cf = (CFBooleanRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
if (CFGetTypeID(cf) != CFBooleanGetTypeID()) {
FF_DEBUG("Expected a CFBoolean, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFBoolean'";
}
@@ -157,6 +179,7 @@ const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result)
const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -166,6 +189,7 @@ const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) {
const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -175,6 +199,7 @@ const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* res
const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -184,16 +209,20 @@ const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* res
const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
if (CFGetTypeID(cf) != CFDataGetTypeID()) {
FF_DEBUG("Expected a CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFData'";
}
CFIndex trueLength = CFDataGetLength((CFDataRef) cf);
if (trueLength < offset + size) {
FF_DEBUG("The CFData holds %ld bytes, the caller asked for [%u, %u)",
(long) trueLength, offset, offset + size);
return "Data length is less than offset + size";
}
@@ -208,6 +237,8 @@ const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offs
const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result) {
CFDictionaryRef cf = (CFDictionaryRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr || CFGetTypeID(cf) != CFDictionaryGetTypeID()) {
FF_DEBUG("Expected a CFDictionary, got %s",
cf == nullptr ? "no value at all" : "a value of another type");
return "TypeID is not 'CFDictionary'";
}
@@ -218,6 +249,7 @@ const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryR
const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
+28 -2
View File
@@ -1,5 +1,6 @@
#include "smc_temps.h"
#include "common/apple/cf_helpers.h"
#include "common/debug.h"
#include "common/strutil.h"
#include <stdint.h>
@@ -83,7 +84,10 @@ static void smcUltostr(char* str, uint32_t val) {
static const char* smcCall(io_connect_t conn, uint32_t selector, SmcKeyData_t* inputStructure, SmcKeyData_t* outputStructure) {
size_t size = sizeof(SmcKeyData_t);
if (IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size) != kIOReturnSuccess) {
const IOReturn callStatus = IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size);
if (callStatus != kIOReturnSuccess) {
FF_DEBUG("IOConnectCallStructMethod(selector=%u, command=%u) failed: IOReturn 0x%08x",
selector, (uint32_t) (unsigned char) inputStructure->data8, (unsigned) callStatus);
return "IOConnectCallStructMethod(conn) failed";
}
return nullptr;
@@ -136,10 +140,13 @@ static const char* smcReadSmcVal(io_connect_t conn, const UInt32Char_t key, SmcV
static const char* smcOpen(io_connect_t* conn) {
FF_IOOBJECT_AUTO_RELEASE io_object_t device = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("AppleSMC"));
if (!device) {
FF_DEBUG("IOKit has no AppleSMC service");
return "No SMC device found";
}
if (IOServiceOpen(device, mach_task_self(), 0, conn) != kIOReturnSuccess) {
const IOReturn openStatus = IOServiceOpen(device, mach_task_self(), 0, conn);
if (openStatus != kIOReturnSuccess) {
FF_DEBUG("IOServiceOpen(AppleSMC) failed: IOReturn 0x%08x", (unsigned) openStatus);
return "IOServiceOpen() failed";
}
@@ -153,6 +160,7 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
return error;
}
if (val.dataSize == 0) {
FF_DEBUG("The SMC key %s reported a data size of 0", key);
return "Empty SMC result";
}
@@ -173,9 +181,12 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
*value = (double) ntohll(*(uint64_t*) (val.bytes));
break;
default:
FF_DEBUG("SMC key %s: unsigned type %s has an unsupported size of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC unsigned integer data size";
}
} else {
FF_DEBUG("SMC key %s: unsupported unsigned type %s", key, val.dataType);
return "Unsupported SMC unsigned data type";
}
break;
@@ -204,9 +215,13 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
} else if (ffStrEquals(val.dataType, "fpe2")) {
*value = ntohs(*(uint16_t*) (val.bytes)) / 4.0;
} else {
FF_DEBUG("SMC key %s: unsupported fixed point type %s of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC floating point data type";
}
} else {
FF_DEBUG("SMC key %s: unsupported floating point type %s of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC floating point data type";
}
break;
@@ -227,6 +242,8 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
*value = (double) ntohll(*(int64_t*) (val.bytes));
break;
default:
FF_DEBUG("SMC key %s: signed type %s has an unsupported size of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC signed integer data size";
}
} else if (val.dataType[1] == 'p' && val.dataSize == 2) // signed fixed point types
@@ -252,9 +269,12 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
} else if (ffStrEquals(val.dataType, "spf0")) {
*value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 1.0;
} else {
FF_DEBUG("SMC key %s: unsupported signed fixed point type %s of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC signed integer data type";
}
} else {
FF_DEBUG("SMC key %s: unsupported signed type %s", key, val.dataType);
return "Unsupported SMC signed data type";
}
break;
@@ -263,11 +283,15 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
if (ffStrEquals(val.dataType, "{pwm") && val.dataSize == 2) {
*value = (double) ntohs(*(uint16_t*) (val.bytes)) * 100 / 65536.0;
} else {
FF_DEBUG("SMC key %s: unsupported special type %s of %u bytes", key, val.dataType, val.dataSize);
return "Unsupported SMC special data type";
}
break;
default:
FF_DEBUG("SMC key %s: unknown data type '%c%c' (%s) of %u bytes",
key, val.dataType[0] ? val.dataType[0] : '?', val.dataType[1] ? val.dataType[1] : '?',
val.dataType, val.dataSize);
return "Unsupported SMC data type";
}
return nullptr;
@@ -300,6 +324,7 @@ const char* ffDetectSmcSpecificTemp(const char* sensor, double* result) {
}
if (!detectTemp(conn, sensor, result)) {
FF_DEBUG("The SMC sensor %s could not be read, or read a value outside 10..120", sensor);
return "Could not read SMC temperature";
}
@@ -490,6 +515,7 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) {
break;
}
FF_DEBUG("None of the SMC sensors for temp type %d reported a usable reading", (int) type);
if (count == 0) {
return "No temperatures detected";
}
+5 -4
View File
@@ -2,8 +2,9 @@
#include "fastfetch.h"
void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output);
static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) {
// `str`, `out_size` and `output` are dereferenced unconditionally
[[gnu::nonnull(2, 3, 4)]] void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output);
[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) {
FFstrbuf out = ffStrbufCreateA(10 + in->length * 4 / 3);
ffBase64EncodeRaw(in->length, in->chars, &out.length, out.chars);
assert(out.length < out.allocated);
@@ -11,8 +12,8 @@ static inline FFstrbuf ffBase64EncodeStrbuf(const FFstrbuf* in) {
return out;
}
bool ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output);
static inline FFstrbuf ffBase64DecodeStrbuf(const FFstrbuf* in) {
[[gnu::nonnull(2, 3, 4)]] void ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output);
[[gnu::nonnull(1), nodiscard]] static inline FFstrbuf ffBase64DecodeStrbuf(const FFstrbuf* in) {
FFstrbuf out = ffStrbufCreateA(10 + in->length * 3 / 4);
ffBase64DecodeRaw(in->length, in->chars, &out.length, out.chars);
assert(out.length < out.allocated);
+12 -28
View File
@@ -1,38 +1,22 @@
#pragma once
#include "fastfetch.h"
#include "common/time.h"
static inline const char* ffFindFileName(const char* file) {
const char* lastSlash = __builtin_strrchr(file, '/');
#ifdef _WIN32
if (lastSlash == nullptr) {
lastSlash = __builtin_strrchr(file, '\\');
}
#endif
if (lastSlash != nullptr) {
return lastSlash + 1;
}
return file;
}
#ifndef NDEBUG
#define FF_DEBUG_PRINT(file_, line_, format_, ...) \
do { \
if (instance.config.display.debugMode) \
fprintf(stderr, "[%s%4d, %s] " format_ "\n", ffFindFileName(file_), line_, ffTimeToTimeStr(ffTimeGetNow()), ##__VA_ARGS__); \
} while (0)
#else
#define FF_DEBUG_PRINT(file_, line_, format_, ...) \
do { \
} while (0)
#endif
#include "fastfetch.h"
#include "common/time.h"
#define FF_DEBUG(format, ...) FF_DEBUG_PRINT(__FILE__, __LINE__, format, ##__VA_ARGS__)
[[gnu::nonnull(1, 3), gnu::format(printf, 3, 4)]]
void ffDebugPrint(const char* file, int line, const char* format, ...);
#if _WIN32
#define FF_DEBUG(format, ...) ffDebugPrint(__FILE__, __LINE__, format, ##__VA_ARGS__)
#if _WIN32
const char* ffDebugWin32Error(DWORD errorCode);
const char* ffDebugNtStatus(NTSTATUS status);
const char* ffDebugConfigRet(unsigned long /*CONFIGRET*/ ret);
const char* ffDebugHResult(HRESULT hr);
#endif
#else
#define FF_DEBUG(format, ...) ((void) 0)
#endif
+1 -1
View File
@@ -2,4 +2,4 @@
#include "fastfetch.h"
void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result);
[[gnu::nonnull(2)]] void ffDurationAppendNum(uint64_t totalSeconds, FFstrbuf* result);
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
// `__BIG_ENDIAN__` alone is not enough: GCC does not define it, it documents only `__BYTE_ORDER__`
// and the `__ORDER_*_ENDIAN__` values. Testing the former silently selected the little-endian
// branch on s390x, where `FF_READ_BE` then byte-swapped values that were already big-endian.
// `defined(__BIG_ENDIAN__)` alone would not do either: a macro that is defined as 0 is not a
// big-endian platform.
#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || (defined(__BIG_ENDIAN__) && __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
+3 -1
View File
@@ -2,4 +2,6 @@
#include "fastfetch.h"
bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result);
// Returns false when nothing was appended (e.g. `mhz == 0`); callers routinely discard that, so it
// is not `nodiscard`.
[[gnu::nonnull(2)]] bool ffFreqAppendNum(uint32_t mhz, FFstrbuf* result);
+1 -1
View File
@@ -27,7 +27,7 @@ void ffPlatformInit(FFPlatform* platform) {
ffStrbufInit(&info->release);
ffStrbufInit(&info->version);
ffStrbufInit(&info->architecture);
info->pageSize = 0;
info->pageSizeShift = 12; // 4096, reasonable fallback
ffPlatformInitImpl(platform);
+71 -34
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,64 @@ 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 12.5% for the table growing between the two calls
size_t fileCapacity = fileSize;
FF_AUTO_FREE struct kinfo_file* kf = (struct kinfo_file*) malloc(fileCapacity);
if (kf) {
int rv;
// The table only grows, so the loop terminates as long as it grows by less
// than 12.5% per retry. Cap it anyway: spinning on a pathological growth
// rate would hang fastfetch, and giving up only costs the executable check
// below, which already assumes the path is correct when the list is missing.
int retries = 5;
while (true) {
fileMib[5] = (int) (fileCapacity / sizeof(struct kinfo_file));
fileSize = fileCapacity; // sysctl takes the size of the buffer as input
rv = sysctl(fileMib, ARRAY_SIZE(fileMib), kf, &fileSize, nullptr, 0);
if (rv == 0 || errno != ENOMEM || retries-- == 0) {
break;
}
break;
// The file table grew between the two calls. The buffer has to grow
// before the retry: the next call advertises fileCapacity as the size
// of kf, and the kernel would copy that many bytes into the old
// allocation.
// OpenBSD writes back the size it needs, FreeBSD only the number of
// bytes it managed to copy, so take the larger one and add ~10% on
// top -- the growth libprocstat uses for this very sysctl.
size_t newCapacity = fileSize > fileCapacity ? fileSize : fileCapacity;
newCapacity += newCapacity / 8;
struct kinfo_file* newKf = (struct kinfo_file*) realloc(kf, newCapacity);
if (newKf == nullptr) {
rv = -1;
break;
}
kf = newKf;
fileCapacity = newCapacity;
}
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;
}
}
// 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 +164,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 +277,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/");
@@ -280,12 +319,10 @@ static void getSysinfo(FFPlatformSysinfo* info, const struct utsname* uts) {
#endif
ffStrbufAppendS(&info->architecture, uts->machine);
#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)
size_t length = sizeof(info->pageSize);
sysctl((int[]) { CTL_HW, HW_PAGESIZE }, 2, &info->pageSize, &length, nullptr, 0);
#else
info->pageSize = (uint32_t) sysconf(_SC_PAGESIZE);
#endif
// _SC_PAGESIZE is one of the sysconf names <unistd.h> requires every implementation to define,
// and the only failure POSIX defines for sysconf is an invalid name, so this does not fail.
// It replaces the former sysctl(CTL_HW, HW_PAGESIZE) call on FreeBSD / macOS / OpenBSD / NetBSD.
info->pageSizeShift = (uint32_t) __builtin_ctzl((unsigned long) sysconf(_SC_PAGESIZE));
}
static void getCwd(FFPlatform* platform) {
+43 -45
View File
@@ -2,13 +2,13 @@
#include "common/io.h"
#include "common/library.h"
#include "common/strutil.h"
#include "common/windows/folders.h"
#include "common/windows/unicode.h"
#include "common/windows/registry.h"
#include "common/windows/nt.h"
#include <stdalign.h>
#include <windows.h>
#include <shlobj.h>
#include <sddl.h>
#define SECURITY_WIN32 1 // For secext.h
@@ -43,43 +43,27 @@ static void getExePath(FFPlatform* platform) {
ffStrbufReplaceAllC(&platform->exePath, '\\', '/');
}
static void getHomeDir(FFPlatform* platform) {
PWSTR pPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_Profile, KF_FLAG_DEFAULT, nullptr, &pPath))) {
ffStrbufSetWS(&platform->homeDir, pPath);
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
} else {
ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE"));
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
}
CoTaskMemFree(pPath);
}
static void getCacheDir(FFPlatform* platform) {
PWSTR pPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &pPath))) {
ffStrbufSetWS(&platform->cacheDir, pPath);
ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
} else {
if (!ffGetKnownFolderPath(FF_KNOWN_FOLDER_LOCAL_APP_DATA, &platform->cacheDir)) {
ffStrbufAppend(&platform->cacheDir, &platform->homeDir);
ffStrbufAppendS(&platform->cacheDir, "AppData/Local/");
return;
}
CoTaskMemFree(pPath);
ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
}
static void platformPathAddKnownFolder(FFlist* dirs, REFKNOWNFOLDERID folderId) {
PWSTR pPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT, nullptr, &pPath))) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateWS(pPath);
CoTaskMemFree(pPath);
ffStrbufReplaceAllC(&buffer, '\\', '/');
ffStrbufEnsureEndsWithC(&buffer, '/');
if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
}
static void platformPathAddKnownFolder(FFlist* dirs, FFKnownFolder folder) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
if (!ffGetKnownFolderPath(folder, &buffer)) {
return;
}
ffStrbufReplaceAllC(&buffer, '\\', '/');
ffStrbufEnsureEndsWithC(&buffer, '/');
if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
}
}
@@ -112,9 +96,9 @@ static void getConfigDirs(FFPlatform* platform) {
}
ffPlatformPathAddHome(&platform->configDirs, platform, ".config/");
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_ProgramData);
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_RoamingAppData);
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_LocalAppData);
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
ffPlatformPathAddHome(&platform->configDirs, platform, "");
}
@@ -126,9 +110,9 @@ static void getDataDirs(FFPlatform* platform) {
platformPathAddEnvSuffix(&platform->dataDirs, "MINGW_PREFIX", "share");
}
ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/");
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_ProgramData);
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_RoamingAppData);
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_LocalAppData);
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
}
@@ -156,9 +140,25 @@ static void getUserName(FFPlatform* platform) {
TOKEN_USER* tokenUser = (TOKEN_USER*) buf;
UNICODE_STRING sidString = { .Buffer = buffer, .Length = 0, .MaximumLength = sizeof(buffer) };
if (NT_SUCCESS(RtlConvertSidToUnicodeString(&sidString, tokenUser->User.Sid, FALSE))) {
ffStrbufSetNWS(&platform->sid, sidString.Length / sizeof(wchar_t), sidString.Buffer);
uint32_t sidLength = sidString.Length / sizeof(wchar_t);
ffStrbufSetNWS(&platform->sid, sidLength, sidString.Buffer);
// The SID is already in hand, so the profile directory costs one registry read rather
// than a second NtQueryInformationToken(). ffGetProfilePath() needs a null-terminated
// string, which RtlConvertSidToUnicodeString() does not produce.
if (sidLength < ARRAY_SIZE(buffer)) {
buffer[sidLength] = L'\0';
ffGetProfilePath(buffer, &platform->homeDir);
}
}
}
if (platform->homeDir.length == 0) {
ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE")); // Last resort; ffGetProfilePath() covers the normal case
}
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
}
static void getHostName(FFPlatform* platform) {
@@ -229,9 +229,7 @@ static void getSystemReleaseAndVersion(FFPlatformSysinfo* info) {
static void getSystemPageSize(FFPlatformSysinfo* info) {
SYSTEM_BASIC_INFORMATION sbi;
if (NT_SUCCESS(NtQuerySystemInformation(SystemBasicInformation, &sbi, sizeof(sbi), nullptr))) {
info->pageSize = sbi.PhysicalPageSize;
} else {
info->pageSize = 4096;
info->pageSizeShift = (uint32_t) __builtin_ctzl(sbi.PhysicalPageSize);
}
}
@@ -282,12 +280,12 @@ void ffPlatformInitImpl(FFPlatform* platform) {
platform->pid = (uint32_t) (uintptr_t) ffGetTeb()->ClientId.UniqueProcess;
getExePath(platform);
getCwd(platform);
getHomeDir(platform);
getUserName(platform);
getCacheDir(platform);
getConfigDirs(platform);
getDataDirs(platform);
getUserName(platform);
getHostName(platform);
getUserShell(platform);
+53
View File
@@ -0,0 +1,53 @@
#include "common/FFcache.h"
// Entries are file scope statics owned by the cache owner, so they can be linked into a list
// without any allocation. The list holds every entry that has been used at least once: an entry
// that was never used has nothing to drop.
static FFcacheEntry* entries = nullptr;
static void registerEntry(FFcacheEntry* entry) {
if (entry->registered) {
return;
}
entry->registered = true;
entry->next = entries;
entries = entry;
}
void* ffCacheGet(FFcacheEntry* entry) {
if (ffCacheBeginInit(entry) && entry->init) {
entry->init(entry->storage);
}
return entry->storage;
}
bool ffCacheBeginInit(FFcacheEntry* entry) {
registerEntry(entry);
if (entry->initialized) {
return false;
}
entry->initialized = true;
return true;
}
void ffCacheInvalidate(FFcacheEntry* entry) {
if (!entry->initialized) {
return;
}
entry->initialized = false;
if (entry->destroy) {
entry->destroy(entry->storage);
}
}
void ffCacheInvalidateAll(void) {
for (FFcacheEntry* entry = entries; entry; entry = entry->next) {
ffCacheInvalidate(entry);
}
}
+26 -35
View File
@@ -21,8 +21,6 @@ void ffStrbufInitA(FFstrbuf* strbuf, uint32_t allocate) {
}
void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
assert(format != nullptr);
char* buffer = nullptr;
int len = vasprintf(&buffer, format, arguments);
assert(len >= 0);
@@ -33,8 +31,6 @@ void ffStrbufInitVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
// Takes ownership of `heapStr`. The caller must not free `heapStr` after calling this
// function; the memory will be managed and freed via the associated FFstrbuf.
void ffStrbufInitMoveNS(FFstrbuf* strbuf, uint32_t length, char* heapStr) {
assert(heapStr != nullptr);
strbuf->length = length;
size_t allocSize = ffMallocUsableSize(heapStr);
if (allocSize == 0) {
@@ -83,6 +79,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));
}
@@ -156,8 +153,6 @@ void ffStrbufAppendTransformS(FFstrbuf* strbuf, const char* value, int (*transfo
}
void ffStrbufAppendVF(FFstrbuf* strbuf, const char* format, va_list arguments) {
assert(format != nullptr);
va_list copy;
va_copy(copy, arguments);
@@ -191,8 +186,6 @@ const char* ffStrbufAppendSUntilC(FFstrbuf* strbuf, const char* value, char unti
}
void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) {
assert(format != nullptr);
va_list arguments;
va_start(arguments, format);
@@ -208,8 +201,6 @@ void ffStrbufSetF(FFstrbuf* strbuf, const char* format, ...) {
}
void ffStrbufAppendF(FFstrbuf* strbuf, const char* format, ...) {
assert(format != nullptr);
va_list arguments;
va_start(arguments, format);
ffStrbufAppendVF(strbuf, format, arguments);
@@ -235,15 +226,11 @@ void ffStrbufPrependC(FFstrbuf* strbuf, char c) {
}
void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
assert(strbuf != nullptr);
if (length == 0) {
ffStrbufClear(strbuf);
return;
}
assert(value != nullptr);
if (strbuf->allocated <= length) {
char* newBuf = malloc(sizeof(char) * (length + 1));
memcpy(newBuf, value, length);
@@ -261,7 +248,8 @@ void ffStrbufSetNS(FFstrbuf* strbuf, uint32_t length, const char* value) {
}
void ffStrbufSet(FFstrbuf* strbuf, const FFstrbuf* value) {
assert(value && value != strbuf);
// `value` is non-null per the `nonnull(2)` contract; the aliasing check cannot be expressed by an attribute
assert(value != strbuf);
if (value->length == 0) {
ffStrbufClear(strbuf);
@@ -646,7 +634,6 @@ void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) {
}
bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) {
assert(lineptr && n && buffer);
assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));
assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));
@@ -677,7 +664,6 @@ bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffe
}
void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) {
assert(buffer && lineptr && n);
assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0));
assert(!*lineptr || (*lineptr >= buffer->chars && *lineptr <= buffer->chars + buffer->length));
@@ -722,16 +708,17 @@ bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf) {
/// @param compLength The length of the separated string to check.
/// @param comp The separated string to check.
/// @param separator The separator character.
///
/// Empty-string handling:
/// - If `strbuf` is empty, it matches only if `comp` contains an empty segment
/// (e.g. "abc::def", "abc:", ":abc") or `comp` itself is empty.
/// - If `comp` is empty (compLength == 0), it matches only if `strbuf` is also empty.
bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
if (strbuf->length == 0) {
return true;
if (__builtin_expect(compLength == 0, false)) {
return strbuf->length == 0;
}
if (compLength == 0) {
return false;
}
for (const char* p = comp; p < comp + compLength;) {
for (const char* p = comp; p <= comp + compLength;) {
const char* colon = memchr(p, separator, (size_t) (comp + compLength - p));
if (colon == nullptr) {
uint32_t remainingLen = (uint32_t) (comp + compLength - p);
@@ -751,15 +738,11 @@ bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const
/// @brief Case insensitive version of ffStrbufMatchSeparatedNS.
bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
if (strbuf->length == 0) {
return true;
if (__builtin_expect(compLength == 0, false)) {
return strbuf->length == 0;
}
if (compLength == 0) {
return false;
}
for (const char* p = comp; p < comp + compLength;) {
for (const char* p = comp; p <= comp + compLength;) {
const char* colon = memchr(p, separator, (size_t) (comp + compLength - p));
if (colon == nullptr) {
uint32_t remainingLen = (uint32_t) (comp + compLength - p);
@@ -801,9 +784,15 @@ int ffStrbufAppendUtf32CodePoint(FFstrbuf* strbuf, uint32_t codepoint) {
/// @param compLength The length of the separated string to check.
/// @param comp The substring to check.
/// @param separator The separator character.
///
/// Empty-string handling is symmetric to ffStrbufMatchSeparatedNS
bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
if (__builtin_expect(strbuf->length == 0, false)) {
return compLength == 0; // An empty list contains only the empty segment
}
uint32_t startIndex = 0;
while (startIndex < strbuf->length) {
while (startIndex <= strbuf->length) { // `<=` so a trailing empty segment (e.g. "abc:") is checked
uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);
uint32_t folderLength = colonIndex - startIndex;
@@ -818,8 +807,12 @@ bool ffStrbufSeparatedContainNS(const FFstrbuf* strbuf, uint32_t compLength, con
}
bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator) {
if (__builtin_expect(strbuf->length == 0, false)) {
return compLength == 0; // An empty list contains only the empty segment
}
uint32_t startIndex = 0;
while (startIndex < strbuf->length) {
while (startIndex <= strbuf->length) { // `<=` so a trailing empty segment (e.g. "abc:") is checked
uint32_t colonIndex = ffStrbufNextIndexC(strbuf, startIndex, separator);
uint32_t folderLength = colonIndex - startIndex;
@@ -834,8 +827,6 @@ bool ffStrbufSeparatedContainIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLeng
}
bool ffStrbufDecodeHexEscapeSequences(FFstrbuf* strbuf) {
assert(strbuf);
if (strbuf->length < 4) {
return false;
}
+11 -11
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];
@@ -20,15 +17,19 @@ void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char*
str += 3;
}
// The bytes have to be widened through `uint8_t`: `char` is signed here, so widening a byte
// with the high bit set would sign extend it and the `& 63` below would then read the extension
// instead of the byte's own bits. The whole groups above are immune because they read through a
// `uint32_t` instead.
if (size % 3 == 1) {
uint64_t n = (uint64_t) *str << 16;
uint64_t n = (uint64_t) (uint8_t) *str << 16;
*out++ = chars[(n >> 18) & 63];
*out++ = chars[(n >> 12) & 63];
*out++ = '=';
*out++ = '=';
} else if (size % 3 == 2) {
uint64_t n = (uint64_t) *str++ << 16;
n |= (uint64_t) *str << 8;
uint64_t n = (uint64_t) (uint8_t) *str++ << 16;
n |= (uint64_t) (uint8_t) *str << 8;
*out++ = chars[(n >> 18) & 63];
*out++ = chars[(n >> 12) & 63];
*out++ = chars[(n >> 6) & 63];
@@ -65,7 +66,7 @@ static void init_decode_table() {
#define next_char(x) uint8_t x = decode_table[(uint8_t) *str++];
bool ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) {
void ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* output) {
if (*(uint64_t*) decode_table == 0) {
init_decode_table();
}
@@ -112,5 +113,4 @@ bool ffBase64DecodeRaw(uint32_t size, const char* str, uint32_t* out_size, char*
*out = '\0';
*out_size = (uint32_t) (out - output);
return true;
}
+11
View File
@@ -1,7 +1,9 @@
#include "common/binary.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/strutil.h"
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
@@ -108,6 +110,7 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset,
if (is_64) {
const struct mach_header_64* header = readData(mapping, sizeof(struct mach_header_64), offset);
if (!header) {
FF_DEBUG("Failed to read the 64-bit mach header at offset %lld", (long long) offset);
return "read mach header failed";
}
@@ -116,6 +119,7 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset,
} else {
const struct mach_header* header = readData(mapping, sizeof(struct mach_header), offset);
if (!header) {
FF_DEBUG("Failed to read the mach header at offset %lld", (long long) offset);
return "read mach header failed";
}
@@ -200,6 +204,7 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset,
static const char* dumpFatHeader(const FFMemoryMapping* mapping, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
const struct fat_header* headerRaw = readData(mapping, sizeof(struct fat_header), 0);
if (!headerRaw) {
FF_DEBUG("Failed to read the fat header");
return "read fat header failed";
}
@@ -251,6 +256,7 @@ static const char* dumpFatHeader(const FFMemoryMapping* mapping, bool (*cb)(cons
return dumpMachHeader(mapping, machHeaderOffset, *magic == MH_MAGIC_64, cb, userdata, minLength);
}
}
FF_DEBUG("No supported Mach-O slice was found in the fat binary");
return "Unsupported fat header";
}
@@ -265,11 +271,13 @@ static const char* dumpFatHeader(const FFMemoryMapping* mapping, bool (*cb)(cons
const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
FF_AUTO_CLOSE_FD int fd = open(machoFile, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(\"%s\") failed: %s", machoFile, strerror(errno));
return "File could not be opened";
}
struct stat st;
if (fstat(fd, &st) != 0 || st.st_size <= 0) {
FF_DEBUG("fstat(\"%s\") failed or the file is empty", machoFile);
return "Failed to stat file";
}
@@ -278,12 +286,14 @@ const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char*
.length = (size_t) st.st_size,
};
if (mapping.data == MAP_FAILED) {
FF_DEBUG("mmap(\"%s\", %lld bytes) failed: %s", machoFile, (long long) st.st_size, strerror(errno));
return "mmap failed";
}
// Read the magic number to determine the type of binary
const uint32_t* magic = readData(&mapping, sizeof(uint32_t), 0);
if (!magic) {
FF_DEBUG("Failed to read the magic number");
return "read magic number failed";
}
@@ -291,6 +301,7 @@ const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char*
// MH_CIGAM and MH_CIGAM_64 seem to be no longer used, as `swap_mach_header` is marked as deprecated.
// However FAT_CIGAM and FAT_CIGAM_64 are still used (/usr/bin/vim).
if (*magic != MH_MAGIC && *magic != MH_MAGIC_64 && *magic != FAT_CIGAM && *magic != FAT_CIGAM_64 && *magic != FAT_MAGIC && *magic != FAT_MAGIC_64) {
FF_DEBUG("Unsupported magic number 0x%08x", (unsigned) *magic);
return "Unsupported format or big endian mach-o file";
}
+7
View File
@@ -1,4 +1,5 @@
#include "common/binary.h"
#include "common/debug.h"
#if defined(FF_HAVE_ELF) || defined(__sun) || (defined(__FreeBSD__) && !defined(__DragonFly__)) || defined(__OpenBSD__) || defined(__NetBSD__)
@@ -43,6 +44,7 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s
FF_LIBRARY_LOAD_MESSAGE(libelf, "libelf" FF_LIBRARY_EXTENSION, 1);
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version)
if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) {
FF_DEBUG("elf_version() failed");
return "elf_version() failed";
}
@@ -60,23 +62,27 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s
}
if (elfData.ffelf_end == nullptr) {
FF_DEBUG("load libelf failed");
return "load libelf failed";
}
// Open the ELF file
FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(%s) failed: %s", elfFile, strerror(errno));
return "open() failed";
}
Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, nullptr);
if (elf == nullptr) {
FF_DEBUG("elf_begin() failed");
return "elf_begin() failed";
}
// Get the section header string table index
size_t shstrndx = 0;
if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0) {
FF_DEBUG("elf_getshdrstrndx() failed");
elfData.ffelf_end(elf);
return "elf_getshdrstrndx() failed";
}
@@ -141,6 +147,7 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s
*/
const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
FF_UNUSED(file, cb, userdata, minLength);
FF_DEBUG("Fastfetch was built without libelf support");
return "Fastfetch was built without libelf support";
}
+9 -2
View File
@@ -1,4 +1,5 @@
#include "common/binary.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/strutil.h"
#include "common/windows/nt.h"
@@ -17,22 +18,28 @@
const char* ffBinaryExtractStrings(const char* peFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileA(peFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
FF_DEBUG("CreateFileA() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateFileA() failed";
}
FF_AUTO_CLOSE_FD HANDLE hSection = nullptr;
if (!NT_SUCCESS(NtCreateSection(&hSection, SECTION_MAP_READ, nullptr, nullptr, PAGE_READONLY, SEC_COMMIT, hFile))) {
NTSTATUS status = NtCreateSection(&hSection, SECTION_MAP_READ, nullptr, nullptr, PAGE_READONLY, SEC_COMMIT, hFile);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtCreateSection() failed: %s", ffDebugNtStatus(status));
return "NtCreateSection() failed";
}
PVOID base = nullptr;
SIZE_T viewSize = 0;
if (!NT_SUCCESS(NtMapViewOfSection(hSection, NtCurrentProcess(), &base, 0, 0, nullptr, &viewSize, ViewUnmap, 0, PAGE_READONLY))) {
status = NtMapViewOfSection(hSection, NtCurrentProcess(), &base, 0, 0, nullptr, &viewSize, ViewUnmap, 0, PAGE_READONLY);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtMapViewOfSection() failed: %s", ffDebugNtStatus(status));
return "NtMapViewOfSection() failed";
}
PIMAGE_NT_HEADERS ntHeaders = RtlImageNtHeader(base);
if (!ntHeaders) {
FF_DEBUG("RtlImageNtHeader() failed");
NtUnmapViewOfSection(NtCurrentProcess(), base);
return "RtlImageNtHeader() failed";
}
+95 -23
View File
@@ -63,64 +63,108 @@ bool ffParseModuleOptions(const char* key, const char* value) {
return false;
}
static yyjson_val* findStructureModuleConfig(FFdata* data, const char* moduleType);
// The module options live in the JSON config only. They must be merged here as well as in
// `parseStructureCommand`, because `ffPrepareXxx` takes the first snapshot while `ffDetectXxx`
// takes the second one. If the two disagree, they list different entries and the deltas are
// meaningless. Keeping both call sites on this single helper is what makes them agree.
static void initStructureModuleOptions(FFdata* data, FFModuleBaseInfo* baseInfo, void* options) {
baseInfo->initOptions(options);
yyjson_val* configModule = findStructureModuleConfig(data, baseInfo->name);
if (configModule != nullptr) {
baseInfo->parseJsonObject(options, configModule);
}
}
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 || !FF_MODULE_DISABLE_COMMAND
case 'C':
case 'c':
FF_IF_MODULE_MATCH(FF_CPUUSAGE_MODULE_NAME)
ffPrepareCPUUsage();
break;
#endif
#endif
#if !FF_MODULE_DISABLE_CPUUSAGE
FF_IF_MODULE_MATCH(ffCPUUsageModuleInfo.name)
ffPrepareCPUUsage(); // The rate is derived from the CPU's own counters; no options are involved
#endif
#if !FF_MODULE_DISABLE_DISKIO
#if !FF_MODULE_DISABLE_COMMAND
FF_IF_MODULE_MATCH(ffCommandModuleInfo.name) {
// `command` runs its shell commands in parallel: `ffPrepareCommand` spawns them here,
// in the prepare pass, and `ffDetectCommand` collects them in the print pass. The
// command line itself can only be read from the JSON config, and this code path does
// not consult it, so nothing would ever be queued and the module would report an
// internal error marker. Refuse it up front instead of letting it get that far.
fputs("Error: module `command` is not supported with `--structure`\n", stderr);
fputs(" Its options can only be read from the JSON config, which `--structure` bypasses.\n", stderr);
fputs(" Add it to the config instead, e.g. `{ \"modules\": [ { \"type\": \"command\", \"text\": \"uname -r\" } ] }`\n", stderr);
exit(481);
}
#endif
#if !FF_MODULE_DISABLE_CPUUSAGE || !FF_MODULE_DISABLE_COMMAND
break;
#endif
#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);
initStructureModuleOptions(data, &ffDiskIOModuleInfo, &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);
initStructureModuleOptions(data, &ffNetIOModuleInfo, &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);
initStructureModuleOptions(data, &ffPublicIPModuleInfo, &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;
initStructureModuleOptions(data, &ffTopModuleInfo, &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);
initStructureModuleOptions(data, &ffWeatherModuleInfo, &options);
ffPrepareWeather(&options);
}
break;
#endif
#endif
}
#undef FF_IF_MODULE_MATCH
@@ -167,6 +211,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,
@@ -176,7 +248,7 @@ static bool parseStructureCommand(
FFModuleBaseInfo* baseInfo = *modules;
if (ffStrEqualsIgnCase(line, baseInfo->name)) {
alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE];
baseInfo->initOptions(optionBuf);
initStructureModuleOptions(data, baseInfo, optionBuf);
if (data->resultDoc != nullptr) {
fn(data, baseInfo, optionBuf);
} else {
+3
View File
@@ -2,6 +2,7 @@
#ifdef FF_HAVE_DBUS
#include "common/debug.h"
#include "common/thread.h"
#include "common/strutil.h"
@@ -39,11 +40,13 @@ static const FFDBusLibrary* loadLib(void) {
const char* ffDBusLoadData(DBusBusType busType, FFDBusData* data) {
data->lib = loadLib();
if (data->lib == nullptr) {
FF_DEBUG("libdbus-1 could not be loaded");
return "Failed to load DBus library";
}
data->connection = data->lib->ffdbus_bus_get(busType, nullptr);
if (data->connection == nullptr) {
FF_DEBUG("dbus_bus_get(busType=%d) returned no connection", (int) busType);
return "Failed to connect to DBus";
}
@@ -1,8 +1,50 @@
#include "common/debug.h"
#include "common/windows/nt.h"
#include <errno.h>
#include <windows.h>
#include <cfgmgr32.h>
#ifndef NDEBUG
#include "common/debug.h"
#include "common/thread.h"
[[gnu::nonnull(1), gnu::pure, gnu::always_inline, nodiscard]]
static inline const char* ffFindFileName(const char* file) {
const char* lastSlash = __builtin_strrchr(file, '/');
#ifdef _WIN32
if (lastSlash == nullptr) {
lastSlash = __builtin_strrchr(file, '\\');
}
#endif
if (lastSlash != nullptr) {
return lastSlash + 1;
}
return file;
}
#if __GNUC__ && !__clang__
[[gnu::optimize("O2")]]
#endif
void ffDebugPrint(const char* file, int line, const char* format, ...) {
if (!instance.config.display.debugMode) {
return;
}
static FFThreadMutex debugMutex = FF_THREAD_MUTEX_INITIALIZER;
ffThreadMutexLock(&debugMutex);
int errno_ = errno;
static char errmsg_[1024];
va_list args;
va_start(args, format);
vsnprintf(errmsg_, sizeof(errmsg_), format, args);
va_end(args);
fprintf(stderr, "[%s%4d, %s] %s\n", ffFindFileName(file), line, ffTimeToTimeStr(ffTimeGetNow()), errmsg_);
errno = errno_;
ffThreadMutexUnlock(&debugMutex);
}
#if _WIN32
#include "common/windows/nt.h"
#include <windows.h>
#include <cfgmgr32.h>
const char* ffDebugWin32Error(DWORD errorCode) {
static char buffer[512];
@@ -58,3 +100,6 @@ static inline DWORD HRESULTToWin32Error(HRESULT hr) {
const char* ffDebugHResult(HRESULT hr) {
return ffDebugWin32Error(HRESULTToWin32Error(hr));
}
#endif
#endif
+77 -205
View File
@@ -147,11 +147,26 @@ static inline bool formatArgSet(const FFformatarg* arg) {
static void appendLuaError(FFstrbuf* buffer, const char* prefix, lua_State* L) {
const char* err = lua_tolstring(L, -1, nullptr);
if (err) {
const char* tmp = strchr(err, ':');
if (tmp) {
err = tmp + 1;
while (*err == ' ') {
++err;
// Drop the position Lua puts in front of an error and keep the message only. Both places
// that build one -- `luaG_addinfo` and `luaL_where` -- format it as `%s:%d: %s`, so a
// position is a colon, a line number, a colon and a space:
// general.preload.lua:1: <name> expected near '=' -> <name> expected near '='
// [string ""]:1: x -> x
// Not every error has one, and cutting at the first colon mangled those. `luaL_loadfilex`
// reports `cannot open <path>: <strerror>`, where that colon is the one after the path, so
// the path -- the only part naming the problem -- was dropped and `No such file or
// directory` was left on its own. A colon followed by a line number does not identify a
// position either, because a path can contain one: `cannot open /tmp/x:12:y/nope.lua: ...`
// has to survive whole. The trailing space is what separates the two, and a real position
// always has it.
for (const char* colon = strchr(err, ':'); colon; colon = strchr(colon + 1, ':')) {
const char* line = colon + 1;
while (*line >= '0' && *line <= '9') {
++line;
}
if (line > colon + 1 && line[0] == ':' && line[1] == ' ') {
err = line + 2;
break;
}
}
}
@@ -205,7 +220,11 @@ static bool parseLuaString(FFstrbuf* buffer, const char* script, uint32_t script
lua_pushboolean(L, *(bool*) arg->value);
break;
case FF_ARG_TYPE_STRING:
lua_pushlstring(L, (const char*) arg->value, strlen((const char*) arg->value));
if (!arg->value) {
lua_pushlstring(L, "", 0);
} else {
lua_pushlstring(L, (const char*) arg->value, strlen((const char*) arg->value));
}
break;
case FF_ARG_TYPE_STRBUF: {
const FFstrbuf* sb = (const FFstrbuf*) arg->value;
@@ -261,208 +280,67 @@ static bool parseLuaString(FFstrbuf* buffer, const char* script, uint32_t script
}
#endif
#if FF_HAVE_QUICKJS
#include <quickjs.h>
struct FFQuickJSData {
FF_LIBRARY_SYMBOL(JS_NewRuntime)
FF_LIBRARY_SYMBOL(JS_NewContext)
FF_LIBRARY_SYMBOL(JS_FreeRuntime)
FF_LIBRARY_SYMBOL(JS_EvalThis)
FF_LIBRARY_SYMBOL(JS_GetException)
FF_LIBRARY_SYMBOL(JS_ToCStringLen2)
FF_LIBRARY_SYMBOL(JS_FreeCString)
FF_LIBRARY_SYMBOL(JS_NewStringLen)
FF_LIBRARY_SYMBOL(JS_NewArray)
FF_LIBRARY_SYMBOL(JS_NewBigUint64)
FF_LIBRARY_SYMBOL(JS_SetPropertyUint32)
FF_LIBRARY_SYMBOL(JS_NewObject)
FF_LIBRARY_SYMBOL(JS_SetPropertyStr)
FF_LIBRARY_SYMBOL(JS_FreeValue)
JSRuntime* rt;
JSContext* ctx;
bool inited;
} qjsData;
static const char* loadQuickJSState(void) {
if (qjsData.inited) {
if (qjsData.ctx == nullptr) {
return "QuickJS is not available";
}
return nullptr;
// Returns the length of the ANSI CSI sequence at `str`, or 0 if there is none.
// A CSI sequence is `ESC [` + parameter bytes (0x30-0x3F) + intermediate bytes (0x20-0x2F) + final byte (0x40-0x7E)
static uint32_t ansiEscapeLength(const char* str) {
if (str[0] != '\e' || str[1] != '[') {
return 0;
}
qjsData.inited = true;
#ifdef _WIN32
FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs-0" FF_LIBRARY_EXTENSION, 0)
#else
FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs" FF_LIBRARY_EXTENSION, 0)
#endif
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewRuntime)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewContext)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeRuntime)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_EvalThis)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_GetException)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_ToCStringLen2)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeCString)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewStringLen)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewArray)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyUint32)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewObject)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyStr)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeValue)
qjsData.rt = qjsData.ffJS_NewRuntime();
if (qjsData.rt == nullptr) {
return "JS_NewRuntime() failed";
const char* p = str + 2;
while (*p >= '0' && *p <= '?') {
++p;
}
qjsData.ctx = qjsData.ffJS_NewContext(qjsData.rt);
if (qjsData.ctx == nullptr) {
qjsData.ffJS_FreeRuntime(qjsData.rt);
qjsData.rt = nullptr;
return "JS_NewContext() failed";
while (*p >= ' ' && *p <= '/') {
++p;
}
libqjs = nullptr; // don't close quickjs
return nullptr;
if (*p < '@' || *p > '~') {
return 0; // not a complete CSI sequence
}
return (uint32_t) (p - str) + 1;
}
static bool parseQuickJSString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) {
const char* err = loadQuickJSState();
if (err) {
ffStrbufAppendF(buffer, "Qjs init error: %s", err);
return false;
}
JSContext* ctx = qjsData.ctx;
JSValue argsObj = qjsData.ffJS_NewObject(ctx);
FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate();
for (uint32_t i = 0; i < numArgs; ++i) {
const FFformatarg* arg = &arguments[i];
JSValue value;
switch (arg->type) {
case FF_ARG_TYPE_INT:
value = JS_NewInt32(ctx, *(int32_t*) arg->value);
break;
case FF_ARG_TYPE_UINT:
value = JS_NewUint32(ctx, *(uint32_t*) arg->value);
break;
case FF_ARG_TYPE_UINT64: {
uint64_t val = *(uint64_t*) arg->value;
if (val <= INT32_MAX) {
value = JS_NewInt32(ctx, (int32_t) val);
} else {
value = JS_NewFloat64(ctx, (double) val);
}
break;
}
case FF_ARG_TYPE_UINT16:
value = JS_NewUint32(ctx, *(uint16_t*) arg->value);
break;
case FF_ARG_TYPE_UINT8:
value = JS_NewUint32(ctx, *(uint8_t*) arg->value);
break;
case FF_ARG_TYPE_FLOAT:
value = JS_NewFloat64(ctx, *(float*) arg->value);
break;
case FF_ARG_TYPE_DOUBLE:
value = JS_NewFloat64(ctx, *(double*) arg->value);
break;
case FF_ARG_TYPE_BOOL:
value = JS_NewBool(ctx, *(bool*) arg->value);
break;
case FF_ARG_TYPE_STRING:
value = qjsData.ffJS_NewStringLen(ctx, (const char*) arg->value, strlen((const char*) arg->value));
break;
case FF_ARG_TYPE_STRBUF: {
const FFstrbuf* sb = (const FFstrbuf*) arg->value;
value = qjsData.ffJS_NewStringLen(ctx, sb->chars, sb->length);
break;
}
case FF_ARG_TYPE_LIST: {
const FFlist* list = (const FFlist*) arg->value;
JSValue arr = qjsData.ffJS_NewArray(ctx);
for (uint32_t li = 0; li < list->length; ++li) {
const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li);
JSValue itemValue = qjsData.ffJS_NewStringLen(ctx, item->chars, item->length);
qjsData.ffJS_SetPropertyUint32(ctx, arr, li, itemValue);
}
value = arr;
break;
}
default:
value = JS_UNDEFINED;
break;
}
if (arg->name && arg->name[0]) {
normalizeArgName(&argNameBuf, arg->name);
} else {
ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1);
}
qjsData.ffJS_SetPropertyStr(ctx, argsObj, argNameBuf.chars, value);
}
JSValue result = qjsData.ffJS_EvalThis(ctx, argsObj, script, scriptLen, "", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT);
qjsData.ffJS_FreeValue(ctx, argsObj);
bool ret = false;
if (JS_IsException(result)) {
JSValue exc = qjsData.ffJS_GetException(ctx);
const char* message = qjsData.ffJS_ToCStringLen2(ctx, nullptr, exc, false);
qjsData.ffJS_FreeValue(ctx, exc);
ffStrbufAppendF(buffer, "Qjs runtime error: %s", message ?: "unknown");
if (message) {
qjsData.ffJS_FreeCString(ctx, message);
}
} else if (JS_IsUndefined(result)) {
ffStrbufAppendS(buffer, "Qjs result error: undefined result");
} else {
size_t len;
const char* res = qjsData.ffJS_ToCStringLen2(ctx, &len, result, false);
if (res) {
ffStrbufAppendNS(buffer, (uint32_t) len, res);
qjsData.ffJS_FreeCString(ctx, res);
}
ret = true;
}
qjsData.ffJS_FreeValue(ctx, result);
return ret;
}
#endif
static bool skipAnsiEscape(FFstrbuf* in, FFstrbuf* out, FFstrbuf* trailingEscape) {
if (__builtin_expect(in->chars[0] == '\e' && in->chars[1] == '[', false)) {
// skip ANSI escape codes at the start of the string for truncation
const char* p = in->chars + 2;
while (*p && (ffCharIsDigit(*p) || *p == ';')) {
++p;
// Skip all ANSI escape codes at the start of the string, so that they don't count
// towards the truncation / padding length. A value may carry more than one of them,
// e.g. `\e[31m\e[1m`.
uint32_t prefixLen = 0;
while (in->length - prefixLen >= 2) {
uint32_t len = ansiEscapeLength(in->chars + prefixLen);
if (len == 0) {
break;
}
if (*p && isascii(*p)) {
++p;
}
uint32_t prefixLen = (uint32_t) (p - in->chars);
ffStrbufAppendNS(out, prefixLen, in->chars);
ffStrbufSubstrAfter(in, prefixLen - 1);
if (trailingEscape) {
// likely have a `CSI m` reset at the end of the string
uint32_t iLastEscape = ffStrbufLastIndexC(in, '\e');
if (iLastEscape != in->length) {
ffStrbufSetNS(trailingEscape, in->length - iLastEscape, in->chars + iLastEscape);
ffStrbufSubstrBefore(in, iLastEscape);
}
}
return true;
prefixLen += len;
}
return false;
if (prefixLen == 0) {
return false;
}
ffStrbufAppendNS(out, prefixLen, in->chars);
ffStrbufSubstrAfter(in, prefixLen - 1);
if (trailingEscape) {
// likely have one or more `CSI m` resets at the end of the string
uint32_t end = in->length;
while (end >= 3) {
uint32_t start = end - 1;
while (start > 0 && in->chars[start] != '\e') {
--start;
}
uint32_t len = ansiEscapeLength(in->chars + start);
if (len == 0 || start + len != end) {
break;
}
end = start;
}
if (end < in->length) {
ffStrbufSetNS(trailingEscape, in->length - end, in->chars + end);
ffStrbufSubstrBefore(in, end);
}
}
return true;
}
static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
@@ -758,12 +636,6 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
}
bool ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
#if FF_HAVE_QUICKJS
if (ffStrbufStartsWithS(formatstr, "qjs:")) {
return parseQuickJSString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments);
}
#endif
#if FF_HAVE_LUA
if (ffStrbufStartsWithS(formatstr, "lua:")) {
// If outputFormat starts with "lua:", treat the rest as a Lua script
+98 -19
View File
@@ -26,15 +26,16 @@
// 0: title
// 1: blank
// 2: logo type row
// 3: output mode row
// 4: blank
// 5: modules title row
// 6: blank
// 7..7+listRows-1: module grid
// 7+listRows: description row
// 8+listRows: help line 1
// 9+listRows: help line 2
#define FF_GEN_CONFIG_LIST_TOP 7
// 3: logo position row
// 4: output mode row
// 5: blank
// 6: modules title row
// 7: blank
// 8..8+listRows-1: module grid
// 8+listRows: description row
// 9+listRows: help line 1
// 10+listRows: help line 2
#define FF_GEN_CONFIG_LIST_TOP 8
#define FF_GEN_CONFIG_BOTTOM_CHROME 3
// Poll wait timeout (ms) while idle. A longer wait avoids frequent full-screen
@@ -66,6 +67,7 @@ typedef struct FFGenConfigUI {
uint32_t cursor;
uint32_t viewOffset;
FFLogoType logoType;
FFLogoPosition logoPosition;
bool fullConfig;
bool confirmingOverwrite;
uint16_t rows;
@@ -165,7 +167,7 @@ static void enterRawMode(void) {
static void getTerminalSize(uint16_t* rows, uint16_t* cols) {
FFTerminalSizeResult size;
if (ffDetectTerminalSize(&size) && size.rows > 0 && size.columns > 0) {
if (ffDetectTerminalSize(&size, true) && size.rows > 0 && size.columns > 0) {
*rows = size.rows;
*cols = size.columns;
} else {
@@ -248,8 +250,8 @@ static void removeAllBreaksSeparators(FFGenConfigUI* ui) {
static void computeLayout(FFGenConfigUI* ui) {
FFGenConfigLayout* layout = &ui->layout;
if (ui->rows < 10) {
ui->rows = 10;
if (ui->rows < 11) {
ui->rows = 11;
}
int32_t listRows = (int32_t) ui->rows - FF_GEN_CONFIG_LIST_TOP - FF_GEN_CONFIG_BOTTOM_CHROME;
layout->listRows = listRows < 0 ? 0 : (uint16_t) listRows;
@@ -367,6 +369,26 @@ static void cycleLogoType(FFGenConfigUI* ui, int delta) {
}
}
static void cycleLogoPosition(FFGenConfigUI* ui, int delta) {
static const FFLogoPosition positions[] = {
FF_LOGO_POSITION_AUTO,
FF_LOGO_POSITION_LEFT,
FF_LOGO_POSITION_RIGHT,
FF_LOGO_POSITION_TOP,
};
uint32_t index = 0;
for (uint32_t i = 0; i < ARRAY_SIZE(positions); ++i) {
if (positions[i] == ui->logoPosition) {
index = i;
break;
}
}
index = (index + (delta > 0 ? 1 : ARRAY_SIZE(positions) - 1)) % ARRAY_SIZE(positions);
ui->logoPosition = positions[index];
}
static uint32_t countSelectedModules(const FFlist* items) {
uint32_t count = 0;
FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) {
@@ -558,7 +580,46 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
}
finishRow(&row, out, ++rowCount == ui->rows);
// Row 3: output mode
// Row 3: logo position
rowInit(&row, cols);
rowAppendVisual(&row, " ");
rowAppendRaw(&row, "\e[" FF_COLOR_MODE_BOLD "m");
rowAppendVisual(&row, "Logo ");
rowAppendRaw(&row, "\e[" FF_COLOR_MODE_UNDERLINE_ "m");
rowAppendVisual(&row, "p");
rowAppendRaw(&row, "\e[24m"); // Reset underline
rowAppendVisual(&row, "osition:");
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET);
rowAppendVisual(&row, " ");
drawLogoOption(&row, "auto", ui->logoPosition == FF_LOGO_POSITION_AUTO);
rowAppendVisual(&row, " ");
drawLogoOption(&row, "left", ui->logoPosition == FF_LOGO_POSITION_LEFT);
rowAppendVisual(&row, " ");
drawLogoOption(&row, "right", ui->logoPosition == FF_LOGO_POSITION_RIGHT);
rowAppendVisual(&row, " ");
drawLogoOption(&row, "top", ui->logoPosition == FF_LOGO_POSITION_TOP);
if (cols >= 120) {
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_SOFT);
switch (ui->logoPosition) {
case FF_LOGO_POSITION_AUTO:
rowAppendVisual(&row, " - Choose left or top from terminal width");
break;
case FF_LOGO_POSITION_RIGHT:
rowAppendVisual(&row, " - Place the logo to the right");
break;
case FF_LOGO_POSITION_TOP:
rowAppendVisual(&row, " - Place the logo above module output");
break;
case FF_LOGO_POSITION_LEFT:
default:
rowAppendVisual(&row, " - Place the logo to the left");
break;
}
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET);
}
finishRow(&row, out, ++rowCount == ui->rows);
// Row 4: output mode
rowInit(&row, cols);
rowAppendVisual(&row, " ");
rowAppendRaw(&row, "\e[" FF_COLOR_MODE_BOLD FF_COLOR_MODE_UNDERLINE_ "m");
@@ -581,11 +642,11 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
}
finishRow(&row, out, ++rowCount == ui->rows);
// Row 4: blank
// Row 5: blank
rowInit(&row, cols);
finishRow(&row, out, ++rowCount == ui->rows);
// Row 5: modules title
// Row 6: modules title
rowInit(&row, cols);
rowAppendVisual(&row, " ");
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_BOLT);
@@ -596,7 +657,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET);
finishRow(&row, out, ++rowCount == ui->rows);
// Row 6: blank
// Row 7: blank
rowInit(&row, cols);
finishRow(&row, out, ++rowCount == ui->rows);
@@ -618,7 +679,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
finishRow(&row, out, ++rowCount == ui->rows);
}
// Row after grid: description
// Row 9+listRows: description
rowInit(&row, cols);
if (ui->cursor < ui->items.length) {
const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor);
@@ -650,7 +711,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) {
rowInit(&row, cols);
rowAppendRaw(&row, "\e[" FF_COLOR_MODE_DIM_ "m");
rowAppendVisual(&row, " l/L logo o minimal/full s/Enter save q/Esc quit g/G top/bottom");
rowAppendVisual(&row, " l/L logo p/P position o minimal/full s/Enter save q/Esc quit g/G top/bottom");
rowAppendRaw(&row, FASTFETCH_TEXT_MODIFIER_RESET);
finishRow(&row, out, ++rowCount == ui->rows);
@@ -904,6 +965,10 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx
cycleLogoType(ui, 1);
} else if (ch == 'L') {
cycleLogoType(ui, -1);
} else if (ch == 'p') {
cycleLogoPosition(ui, 1);
} else if (ch == 'P') {
cycleLogoPosition(ui, -1);
} else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') {
return 0;
}
@@ -940,6 +1005,18 @@ static FFLogoType initialLogoType(void) {
}
}
static FFLogoPosition initialLogoPosition(void) {
switch (instance.config.logo.position) {
case FF_LOGO_POSITION_TOP:
case FF_LOGO_POSITION_RIGHT:
case FF_LOGO_POSITION_AUTO:
return instance.config.logo.position;
case FF_LOGO_POSITION_LEFT:
default:
return FF_LOGO_POSITION_LEFT;
}
}
static bool applyConfig(FFdata* data, const FFlist* items, bool fullConfig) {
if (data->resultDoc) {
fputs("Error: duplicated `--gen-config` or `--format json` flags found\n", stderr);
@@ -972,6 +1049,7 @@ bool ffGenConfigInteractive(FFdata* data) {
.cursor = 0,
.viewOffset = 0,
.logoType = initialLogoType(),
.logoPosition = initialLogoPosition(),
.fullConfig = false,
.confirmingOverwrite = false,
.rows = 24,
@@ -993,10 +1071,11 @@ bool ffGenConfigInteractive(FFdata* data) {
if (result == 1) {
if (applyConfig(data, &ui.items, ui.fullConfig)) {
instance.config.logo.type = ui.logoType;
instance.config.logo.position = ui.logoPosition;
success = true;
}
} else {
fputs("\nConfig generation cancelled.\n", stderr);
fputs("\nConfig generation canceled.\n", stderr);
}
ffListDestroy(&ui.items);
+3 -12
View File
@@ -178,9 +178,6 @@ void ffDestroyInstance(void) {
#if FF_HAVE_LUA
#include <lua.h>
#endif
#if FF_HAVE_QUICKJS
#include <quickjs.h>
#endif
// Must be in a file compiled with the libfastfetch target, because the FF_HAVE* macros are not defined for the executable targets
void ffListFeatures(void) {
@@ -248,12 +245,6 @@ void ffListFeatures(void) {
#if FF_HAVE_FREETYPE
"freetype\n"
#endif
#if FF_HAVE_PULSE
"libpulse\n"
#endif
#if FF_HAVE_DDCUTIL
"libddcutil\n"
#endif
#if FF_HAVE_ELF || __sun || (__FreeBSD__ && !__DragonFly__) || __OpenBSD__ || __NetBSD__
"libelf\n"
#endif
@@ -281,6 +272,9 @@ void ffListFeatures(void) {
#if FF_ENABLE_WCWIDTH
"Embedded wcwidth\n"
#endif
#if FF_HAVE_SIXEL
"Embedded sixel\n"
#endif
#if FF_HAVE_WINRT
"WinRT headers\n"
#endif
@@ -292,9 +286,6 @@ void ffListFeatures(void) {
#endif
#if FF_HAVE_LUA
LUA_VERSION "\n"
#endif
#if FF_HAVE_QUICKJS
"QuickJS " FF_STR(QJS_VERSION_MAJOR) "." FF_STR(QJS_VERSION_MINOR) "." FF_STR(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX "\n"
#endif
"",
stdout);
+37 -2
View File
@@ -1,5 +1,6 @@
#include "common/io.h"
#include "fastfetch.h"
#include "common/debug.h"
#include "common/strutil.h"
#include "common/time.h"
@@ -11,6 +12,7 @@
#include <poll.h>
#else
#include <sys/select.h>
#define st_mtim st_mtimespec // `struct stat` spells it `st_mtimespec` on Apple
#endif
#if FF_HAVE_WORDEXP
@@ -40,9 +42,11 @@ bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) {
createSubfolders(fileName);
fd = open(fileName, openFlagsModes, openFlagsRights);
if (fd == -1) {
FF_DEBUG("open(%s) failed after creating the parent folders: %s", fileName, strerror(errno));
return false;
}
} else {
FF_DEBUG("open(%s) failed: %s", fileName, strerror(errno));
return false;
}
}
@@ -145,16 +149,19 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
if (ftty < 0) {
ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
if (ftty < 0) {
FF_DEBUG("open(/dev/tty) failed: %s", strerror(errno));
return "open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC) failed";
}
if (tcgetattr(ftty, &oldTerm) == -1) {
FF_DEBUG("tcgetattr(/dev/tty) failed: %s", strerror(errno));
return "tcgetattr(STDIN_FILENO, &oldTerm) failed";
}
struct termios newTerm = oldTerm;
newTerm.c_lflag &= (tcflag_t) ~(ICANON | ECHO);
if (tcsetattr(ftty, TCSAFLUSH, &newTerm) == -1) {
FF_DEBUG("tcsetattr(/dev/tty) failed: %s", strerror(errno));
return "tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTerm)";
}
atexit(restoreTerm);
@@ -164,7 +171,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
// Give the terminal some time to respond
#ifndef __APPLE__
if (poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS) <= 0) {
const int pollResult = poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS);
if (pollResult <= 0) {
FF_DEBUG("poll(/dev/tty) returned %d after %d ms: %s",
pollResult, FF_IO_TERM_RESP_WAIT_MS, pollResult == 0 ? "timed out" : strerror(errno));
return "poll(/dev/tty) timeout or failed";
}
#else
@@ -174,7 +184,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
fd_set rd;
FD_ZERO(&rd);
FD_SET(ftty, &rd);
if (select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 }) <= 0) {
const int selectResult = select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 });
if (selectResult <= 0) {
FF_DEBUG("select(/dev/tty) returned %d after %d ms: %s",
selectResult, FF_IO_TERM_RESP_WAIT_MS, selectResult == 0 ? "timed out" : strerror(errno));
return "select(/dev/tty) timeout or failed";
}
}
@@ -190,6 +203,8 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1);
if (nRead <= 0) {
FF_DEBUG("read(/dev/tty) returned %zd after %zu bytes: %s",
nRead, bytesRead, nRead == 0 ? "the terminal closed the connection" : strerror(errno));
va_end(args);
return "read(STDIN_FILENO, buffer, sizeof(buffer) - 1) failed";
}
@@ -203,6 +218,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
va_end(cargs);
if (ret <= 0) {
FF_DEBUG("vsscanf(\"%s\") matched %d of %d parameters", buffer, ret, nParams);
va_end(args);
return "vsscanf(buffer, format, args) failed";
}
@@ -328,6 +344,25 @@ FFNativeFD ffGetNullFD(void) {
return hNullFile;
}
bool ffIsTerminal(int fd) {
return isatty(fd) != 0;
}
bool ffRemoveFile(const char* fileName) {
return unlink(fileName) == 0;
}
uint64_t ffPathGetMtime(const char* path) {
struct stat st;
if (stat(path, &st) != 0) {
return 0;
}
// An mtime at or before the Unix epoch means the filesystem did not fill it in. Reporting it
// as-is would hand the caller a value indistinguishable from "unknown", so treat it as such.
if (st.st_mtim.tv_sec <= 0) {
return 0;
}
return (uint64_t) st.st_mtim.tv_sec * 1000ull + (uint64_t) st.st_mtim.tv_nsec / 1000000ull;
}
+88 -12
View File
@@ -1,11 +1,27 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/strutil.h"
#include "common/time.h"
#include "common/windows/nt.h"
#include "common/windows/unicode.h"
#include <windows.h>
// TODO: long paths on Windows.
//
// Every path that crosses into the wide API from this file is converted into a fixed
// `wchar_t[MAX_PATH]` buffer, and `RtlUTF8ToUnicodeN` refuses to fill it for a path of MAX_PATH
// characters or more (STATUS_BUFFER_TOO_SMALL) -- which makes each of those call sites a silent
// failure above that length rather than an error anyone sees. Raising a buffer on its own does not
// help: the wide APIs accept a longer path only either with a `\\?\` prefix on every absolute path
// (which also switches off the normalization the rest of the code relies on) or with a
// `longPathAware` manifest plus the LongPathsEnabled registry value.
//
// The plan is the second route: define MAX_PATH as 32768 program-wide, add the manifest flag, and
// size these buffers from the UTF-8 input rather than from the constant. It has to be done in one
// pass, which is why it is recorded here instead of being applied at the call sites the review
// happened to look at.
static bool createSubfolders(wchar_t* fileName) {
HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle;
bool closeRoot = false;
@@ -159,7 +175,9 @@ bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) {
FF_AUTO_CLOSE_FD HANDLE handle = CreateFileW(fileNameW, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_PATH_NOT_FOUND) {
DWORD errorCode = GetLastError();
FF_DEBUG("Failed to open file: %s - %s", fileName, ffDebugWin32Error(errorCode));
if (errorCode == ERROR_PATH_NOT_FOUND) {
if (!createSubfolders(fileNameW)) {
return false;
}
@@ -344,8 +362,18 @@ void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indenta
}
ffStrbufAppendC(folder, '*');
WIN32_FIND_DATAA entry;
HANDLE hFind = FindFirstFileA(folder->chars, &entry);
// The wide API on purpose: every path fastfetch holds is UTF-8, and FindFirstFileA would read
// it in the active code page, so a directory whose name the code page cannot represent -- a
// Chinese or Japanese name under an English or German locale, for one -- answers
// ERROR_PATH_NOT_FOUND and the whole listing disappears. The names it returns are converted
// back to UTF-8 below.
wchar_t wideFolder[PATH_MAX];
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(wideFolder, sizeof(wideFolder), nullptr, folder->chars, folder->length + 1))) {
ffStrbufTrimRight(folder, '*');
return;
}
WIN32_FIND_DATAW entry;
HANDLE hFind = FindFirstFileW(wideFolder, &entry);
ffStrbufTrimRight(folder, '*');
if (hFind == INVALID_HANDLE_VALUE) {
return;
@@ -353,14 +381,15 @@ void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indenta
do {
if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (ffStrEquals(entry.cFileName, ".") || ffStrEquals(entry.cFileName, "..")) {
if (entry.cFileName[0] == L'.') {
continue;
}
ffStrbufSubstrBefore(folder, folderLength);
ffStrbufAppendS(folder, entry.cFileName);
ffStrbufAppendWS(folder, entry.cFileName);
ffStrbufAppendC(folder, '/');
listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), entry.cFileName, pretty);
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(entry.cFileName);
listFilesRecursively(baseLength, folder, (uint8_t) (indentation + 1), name.chars, pretty);
ffStrbufSubstrBefore(folder, folderLength);
continue;
}
@@ -373,8 +402,9 @@ void listFilesRecursively(uint32_t baseLength, FFstrbuf* folder, uint8_t indenta
fputs(folder->chars + baseLength, stdout);
}
puts(entry.cFileName);
} while (FindNextFileA(hFind, &entry));
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateWS(entry.cFileName);
ffStrbufPutTo(&name, stdout);
} while (FindNextFileW(hFind, &entry));
FindClose(hFind);
}
@@ -411,7 +441,9 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
}
while (true) {
if (NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0) {
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
if (status != STATUS_WAIT_0) {
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
SetConsoleMode(hInput, inputMode);
return "NtWaitForSingleObject() failed or timeout";
}
@@ -441,7 +473,9 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
while (true) {
DWORD bytes = 0;
if (!ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr) || bytes == 0) {
BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr);
if (!readOk || bytes == 0) {
FF_DEBUG("ReadFile() %s, bytes=%lu: %s", readOk ? "returned no data" : "failed", bytes, readOk ? "no error" : ffDebugWin32Error(GetLastError()));
va_end(args);
return "ReadFile() failed";
}
@@ -460,6 +494,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
if (ret <= 0) {
va_end(args);
FF_DEBUG("vsscanf(buffer, format, args) failed: %s", strerror(errno));
return "vsscanf(buffer, format, args) failed";
}
if (ret >= nParams) {
@@ -496,6 +531,47 @@ FFNativeFD ffGetNullFD(void) {
return hNullFile;
}
bool ffRemoveFile(const char* fileName) {
return DeleteFileA(fileName) != FALSE;
bool ffIsTerminal(int fd) {
intptr_t handle = _get_osfhandle(fd);
if (handle == -1 || handle == -2) {
// -1: an invalid fd; -2: a valid fd with no file behind it (`_get_osfhandle`'s way of
// saying FF_INVALID_FD). Neither is a terminal.
return false;
}
DWORD mode;
return GetConsoleMode((HANDLE) handle, &mode) != 0;
}
bool ffRemoveFile(const char* fileName) {
wchar_t fileNameW[MAX_PATH]; // see the long path TODO at the top of this file
ULONG len;
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) {
return false;
}
bool ret = DeleteFileW(fileNameW) != FALSE;
FF_DEBUG("Deleting file: %s - %s", fileName, ret ? "Success" : ffDebugWin32Error(GetLastError()));
return ret;
}
uint64_t ffPathGetMtime(const char* path) {
wchar_t fileNameW[MAX_PATH]; // see the long path TODO at the top of this file
ULONG len;
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, path, (ULONG) strlen(path) + 1))) {
return 0;
}
FF_AUTO_CLOSE_FD HANDLE handle = CreateFileW(fileNameW, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) { // file doesn't exist or isn't accessible
return 0;
}
FILE_BASIC_INFORMATION fileInfo;
IO_STATUS_BLOCK iosb;
if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileBasicInformation))) {
return 0;
}
return ffFileTimeToUnixMs((uint64_t) fileInfo.LastWriteTime.QuadPart);
}
+88 -7
View File
@@ -1,5 +1,6 @@
#include "fastfetch.h"
#include "common/color.h"
#include "common/debug.h"
#include "common/jsonconfig.h"
#include "common/printing.h"
#include "common/io.h"
@@ -60,6 +61,22 @@ void ffJsonConfigGenerateModuleArgsConfig(yyjson_mut_doc* doc, yyjson_mut_val* m
}
}
bool ffJsonConfigParseUInt32(yyjson_val* val, uint32_t* result, uint32_t max) {
if (!yyjson_is_int(val)) {
return false;
}
// `yyjson_get_sint()` is exact for every value that fits into `int64_t`, and is negative for the
// `uint64_t` values above `INT64_MAX`, which no caller accepts anyway.
int64_t num = unsafe_yyjson_get_sint(val);
if (num < 0 || (uint64_t) num > max) {
return false;
}
*result = (uint32_t) num;
return true;
}
const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair pairs[]) {
if (yyjson_is_int(val)) {
int intVal = yyjson_get_int(val);
@@ -71,6 +88,7 @@ const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair p
}
}
FF_DEBUG("The enum value %d is not one of the accepted integers", intVal);
return "Invalid enum integer";
} else if (yyjson_is_str(val)) {
const char* strVal = yyjson_get_str(val);
@@ -81,8 +99,11 @@ const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair p
}
}
FF_DEBUG("The enum string \"%s\" is not one of the accepted names", strVal);
return "Invalid enum string";
} else {
FF_DEBUG("The enum value is of JSON type %s, not a string or an integer",
yyjson_get_type_desc(val));
return "Invalid enum value type; must be a string or integer";
}
}
@@ -133,12 +154,15 @@ static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_
static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
switch (type[0]) {
#if !FF_MODULE_DISABLE_CPUUSAGE
// Both `cpuusage` and `command` start with a c, and they are disabled by separate options,
// so the block has to survive either one being off -- otherwise a build with only the other
// one enabled would never prepare this module (see the same guard in `commandoption.c`).
#if !FF_MODULE_DISABLE_CPUUSAGE || !FF_MODULE_DISABLE_COMMAND
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 +177,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 +192,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 +207,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 +219,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) {
@@ -239,6 +278,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
assert(root);
if (!yyjson_is_obj(root)) {
FF_DEBUG("The config root is of JSON type %s", yyjson_get_type_desc(root));
return "Invalid JSON config format. Root value must be an object";
}
@@ -247,6 +287,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
return nullptr;
}
if (!yyjson_is_arr(modules)) {
FF_DEBUG("'modules' is of JSON type %s", yyjson_get_type_desc(modules));
return "Property 'modules' must be an array of strings or objects";
}
@@ -261,6 +302,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
}
yyjson_val* module = item;
bool gatedOnSucceeded = false;
const char* type = yyjson_get_str(module);
if (type) {
module = nullptr;
@@ -268,6 +310,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
yyjson_val* conditions = yyjson_obj_get(module, "condition");
if (conditions) {
if (!yyjson_is_obj(conditions)) {
FF_DEBUG("'condition' is of JSON type %s", yyjson_get_type_desc(conditions));
return "Property 'condition' must be an object";
}
@@ -294,8 +337,43 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
yyjson_val* previousSucceeded = yyjson_obj_get(conditions, "succeeded");
if (previousSucceeded && !unsafe_yyjson_is_null(previousSucceeded)) {
if (!unsafe_yyjson_is_bool(previousSucceeded)) {
FF_DEBUG("'condition.succeeded' is of JSON type %s",
yyjson_get_type_desc(previousSucceeded));
return "Property 'succeeded' in 'condition' must be a boolean";
}
gatedOnSucceeded = true;
if (prepare) {
// Whether this module is printed at all depends on the result of the previous
// one, which is only known in the print pass. Preparing it here would leave a
// result behind that no print pass ever consumes, and since the modules are
// paired by position (see `ffPrepareCommand` / `ffDetectCommand`), that
// would silently shift every later module onto the wrong result.
// Skip it, and let the module report the missing preparation if it turns out
// to be printed after all.
continue;
}
#if !FF_MODULE_DISABLE_COMMAND
// A `command` module that runs in parallel is exactly the pairing above, so this
// combination cannot be left to the print pass: the module would be handed the
// result prepared for the next `command` module and print that instead of its
// own output. Refused here rather than in the prepare pass, because returning
// from there would leave every later module unprepared -- which the print pass
// would then report as a failure of theirs.
// `parallel` is on by default, so leaving the key out is the parallel case too.
if (gatedOnSucceeded && ffStrEqualsIgnCase(yyjson_get_str(yyjson_obj_get(module, "type")), ffCommandModuleInfo.name)) {
yyjson_val* parallel = yyjson_obj_get(module, "parallel");
if (parallel == nullptr || yyjson_get_bool(parallel)) {
FF_DEBUG("The command module at index %zu is gated on 'condition.succeeded' and runs in parallel", idx);
return "Module \"command\" cannot be combined with \"condition.succeeded\" while it runs in "
"parallel: whether it is printed is only known in the print pass, so it cannot be "
"prepared, and the prepared results are matched to modules by position. Set "
"\"parallel\": false on it, or drop the condition.";
}
}
#endif
if (succeeded != unsafe_yyjson_get_bool(previousSucceeded)) {
continue;
}
@@ -304,12 +382,14 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
type = yyjson_get_str(yyjson_obj_get(module, "type"));
if (!type) {
FF_DEBUG("The module object at index %zu has no \"type\" key, or it is not a string", idx);
return "module object must contain a \"type\" key ( case sensitive )";
}
if (yyjson_obj_size(module) == 1) { // contains only Property type
module = nullptr;
}
} else {
FF_DEBUG("The modules entry at index %zu is of JSON type %s", idx, yyjson_get_type_desc(module));
return "modules must be an array of strings or objects";
}
@@ -350,6 +430,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
return nullptr;
}
[[gnu::cold]]
void ffPrintJsonConfig(FFdata* data, bool prepare) {
yyjson_mut_doc* jsonDoc = data->resultDoc;
const char* error = printJsonConfig(data, prepare);
+2 -2
View File
@@ -161,8 +161,8 @@ void* dlsym(void* handle, const char* symbol) {
}
void* ffLibraryGetModule(const wchar_t* libraryFileName) {
assert(libraryFileName != nullptr && "Use \"ffGetPeb()->ImageBaseAddress\" instead");
// `libraryFileName` is non-null per the `nonnull(1)` contract.
// Prefer `ffGetPeb()->ImageBaseAddress` over looking the main module up by name.
void* module = nullptr;
USHORT libraryFileNameBytes = (USHORT) (wcslen(libraryFileName) * sizeof(wchar_t) + sizeof(wchar_t));
NTSTATUS status = LdrGetDllHandle(nullptr, nullptr, &(UNICODE_STRING) {
+3
View File
@@ -1,6 +1,7 @@
#if FF_HAVE_LUA
#include "common/lua.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
struct FFLuaData luaData;
@@ -141,6 +142,7 @@ static int yyjsonEncode(lua_State* L) {
const char* ffLuaLoadState(void) {
if (luaData.inited) {
if (luaData.L == nullptr) {
FF_DEBUG("The Lua state was already initialised and is gone");
return "Lua library is not available";
}
return nullptr;
@@ -212,6 +214,7 @@ const char* ffLuaLoadState(void) {
lua_State* L = ffluaL_newstate();
if (L == nullptr) {
FF_DEBUG("luaL_newstate() could not allocate an interpreter");
return "luaL_newstate() failed";
}
#if LUA_VERSION_NUM >= 505
+39 -14
View File
@@ -1,22 +1,47 @@
#include "common/netif.h"
#include "common/FFcache.h"
#ifndef _WIN32
#include <net/if.h>
#include <netinet/in.h>
#endif
#include <string.h>
// netio, localip and dns resolve the default route on every round, and it can change while a
// `--dynamic-interval` run is going on: a laptop switching from Wi-Fi to Ethernet, a VPN coming up.
static FFNetifDefaultRouteResult defaultRouteV4;
static FFNetifDefaultRouteResult defaultRouteV6;
static void resetDefaultRoute(void* storage) {
// The impls only write the fields they found, so without this a round that finds no default
// route would keep reporting the interface of the previous one.
memset(storage, 0, sizeof(FFNetifDefaultRouteResult));
}
static void initDefaultRouteV4(void* storage) {
FFNetifDefaultRouteResult* result = storage;
result->status = ffNetifGetDefaultRouteImplV4(result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
static void initDefaultRouteV6(void* storage) {
FFNetifDefaultRouteResult* result = storage;
result->status = ffNetifGetDefaultRouteImplV6(result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
static FFcacheEntry ffCacheEntryDefaultRouteV4 = {
.name = "default route V4",
.storage = &defaultRouteV4,
.init = initDefaultRouteV4,
.destroy = resetDefaultRoute,
};
static FFcacheEntry ffCacheEntryDefaultRouteV6 = {
.name = "default route V6",
.storage = &defaultRouteV6,
.init = initDefaultRouteV6,
.destroy = resetDefaultRoute,
};
const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void) {
static FFNetifDefaultRouteResult result;
if (result.status == FF_NETIF_UNINITIALIZED) {
result.status = ffNetifGetDefaultRouteImplV4(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
return &result;
return ffCacheGet(&ffCacheEntryDefaultRouteV4);
}
const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void) {
static FFNetifDefaultRouteResult result;
if (result.status == FF_NETIF_UNINITIALIZED) {
result.status = ffNetifGetDefaultRouteImplV6(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
return &result;
return ffCacheGet(&ffCacheEntryDefaultRouteV6);
}

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