Compare commits

...

47 Commits

Author SHA1 Message Date
Carter Li b385f6ce8d Update SignPath action version in workflow 2026-10-04 23:44:16 -05:00
dependabot[bot] 957e0d08e2 CI: Bump the github-actions group with 6 updates
Bumps the github-actions group with 6 updates:

| 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` |
| [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) | `2.3` | `3.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)

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: 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
- 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>
2026-10-04 18:06:45 +00: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
338 changed files with 6381 additions and 2093 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,symbole,connexion,comando,processus,lokale,adres,titel,som,environnement,Sistem,Lokal,Lokal,Sistem,Paket,Hava,statics,IIterator,whitelist
+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
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
run: cp .github/benchmark-index.html "$RUNNER_TEMP/benchmark-index.html"
- name: store benchmark result
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
uses: benchmark-action/github-action-benchmark@4322e5726e6334590d251fc4f92bec0efafc45dc # v1.22.2
with:
name: Fastfetch
tool: customSmallerIsBetter
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+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@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.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
+4 -4
View File
@@ -43,7 +43,7 @@ jobs:
run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- name: install required packages
run: sudo apt-get update && sudo apt-get install -y gcc-13 jq libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
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@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
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@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
- name: list features
run: ./fastfetch --list-features
+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_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 .
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@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.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@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.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
+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}
+1 -1
View File
@@ -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 -DENABLE_IMAGE_LOGO=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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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@a11f407d3720a3717673d4194f19ee63b284977e # v1.4.0
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
+3 -3
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 }}-jq mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
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
-6
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
+25
View File
@@ -1,3 +1,28 @@
# 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:
+21 -31
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
project(fastfetch
VERSION 2.69.0
VERSION 2.70.0
LANGUAGES C
DESCRIPTION "Fast neofetch-like system information tool"
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
@@ -92,8 +92,6 @@ cmake_dependent_option(ENABLE_EGL "Enable egl" ON "LINUX OR FreeBSD OR OpenBSD O
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)
@@ -123,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" OFF)
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")
@@ -283,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 #
#######################
@@ -463,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
@@ -707,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
@@ -1094,10 +1109,10 @@ 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
@@ -1845,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)
@@ -1954,10 +1956,10 @@ elseif(APPLE)
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
@@ -2275,15 +2277,6 @@ if (BUILD_TESTS)
PRIVATE libfastfetch
)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_executable(fastfetch-test-cpu
tests/cpu.c
)
target_link_libraries(fastfetch-test-cpu
PRIVATE libfastfetch
)
endif()
add_executable(fastfetch-test-strbuf
tests/strbuf.c
)
@@ -2397,9 +2390,6 @@ if (BUILD_TESTS)
)
enable_testing()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_test(NAME test-cpu COMMAND fastfetch-test-cpu)
endif()
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)
+1 -6
View File
@@ -96,7 +96,7 @@ cmake -DBUILD_TESTS=On -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_X11=OFF
Required: CMake ≥ 3.21 and a C23 compiler (GCC, Clang or MSVC). Everything else is optional.
Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, pulse, ddcutil, elf, libzfs, and more.
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.
---
@@ -587,10 +587,6 @@ clang-format -i src/modules/foo/*.c src/modules/foo/*.h
| Module options | `FF<Name>Options` | `FFCPUOptions` |
| Detection results | `FF<Name>Result` | `FFCPUResult` |
### Spelling
CI runs codespell (`.codespellrc`). Known false positives are listed in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code.
### Compiler warnings
The build enables `-Wall -Wextra -Wconversion` plus several `-Werror`s:
@@ -707,7 +703,6 @@ Coverage focuses on the core data structures and the formatting engine in `commo
```sh
clang-format -i <changed files> # format
codespell # spelling
cmake -B build -DBUILD_TESTS=On && cmake --build build -j
cd build && ctest --output-on-failure # tests
./build/fastfetch --format json # verify JSON output is well-formed
+19 -37
View File
@@ -9,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)
@@ -36,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)
@@ -248,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
@@ -260,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
;;
+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
+1
View File
@@ -630,6 +630,7 @@
"cs": "Czech",
"de": "German",
"es": "Spanish",
"fi": "Finnish",
"fr": "French",
"gl": "Galician",
"he": "Hebrew",
+7 -2
View File
@@ -1123,6 +1123,11 @@
"const": "es",
"description": "Spanish"
},
{
"type": "string",
"const": "fi",
"description": "Finnish"
},
{
"type": "string",
"const": "fr",
@@ -1914,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",
@@ -4073,7 +4078,7 @@
"const": "publicip"
},
"url": {
"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 HTTP compression and TCP Fast Open enabled. A non-empty URL disables both and makes `ipv6` have no effect",
"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"
},
+30 -5
View File
@@ -1,5 +1,8 @@
#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>
@@ -7,14 +10,18 @@
const char* ffBinderOpen(FFBinder* binder) {
*binder = (FFBinder) { .fd = -1, .shared = nullptr, .sharedSize = 0, .protocolVersion = 0 };
const int fd = open(FF_BINDER_DEVICE, O_RDWR | O_CLOEXEC);
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 };
if (ioctl(fd, BINDER_VERSION, &version) != 0 || version.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
close(fd);
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";
}
@@ -22,7 +29,7 @@ const char* ffBinderOpen(FFBinder* binder) {
// 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) {
close(fd);
FF_DEBUG("mmap(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
return "Failed to mmap " FF_BINDER_DEVICE;
}
@@ -32,6 +39,7 @@ const char* ffBinderOpen(FFBinder* binder) {
.sharedSize = FF_BINDER_SHARED_SIZE,
.protocolVersion = version.protocol_version,
};
fd = -1; // ownership moved into *binder, which ffBinderClose() lets go
return nullptr;
}
@@ -55,6 +63,8 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
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";
}
@@ -86,6 +96,7 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
.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";
}
@@ -103,8 +114,10 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
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:
@@ -123,6 +136,8 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
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;
@@ -140,6 +155,8 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
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);
@@ -211,6 +228,7 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
// 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";
}
@@ -218,10 +236,13 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
}
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";
}
@@ -238,12 +259,16 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t
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";
}
if (ffBinderReadI32(reply.data, reply.size, 0) != 0) {
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";
}
+436 -128
View File
@@ -1,9 +1,12 @@
#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>
#include <unistd.h>
#ifdef FF_HAVE_ZLIB
#include "common/library.h"
@@ -32,19 +35,48 @@
#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. The central directory would carry the same
// fields, but the local header sits right in front of the data, so one walk over the file finds
// both the bounds and the payload.
// 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_METHOD 8
#define FF_ZIP_OFF_COMPRESSED_SIZE 18
#define FF_ZIP_OFF_UNCOMPRESSED_SIZE 22
#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) {
@@ -96,18 +128,34 @@ static uint32_t dexUleb128(const uint8_t** p, const uint8_t* end) {
}
// 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_INT = 0x04`, sign-extended four-byte integer).
// 0x1f is VALUE_BOOLEAN in that same table, which is the trap: naming it VALUE_INT and comparing
// against it rejects every real int and accepts booleans.
#define FF_DEX_VALUE_INT 0x04
// `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 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
// 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.
//
@@ -121,6 +169,15 @@ static bool dexEncodedValue(const uint8_t** p, const uint8_t* end, int32_t* resu
}
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;
@@ -173,12 +230,18 @@ static const char* dexString(const uint8_t* dex, const uint8_t* end, uint32_t in
return memchr(p, '\0', (size_t) (end - p)) != nullptr ? (const char*) p : nullptr;
}
// Walks the class's static field list and the class's static value array in step, and returns the
// value paired with `fieldName`. 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 returned.
static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* classDescriptor, const char* fieldName, int32_t* result) {
const uint8_t* end = dex + size;
// 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";
}
@@ -186,25 +249,13 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
if (fileSize > size || fileSize < FF_DEX_HEADER_SIZE) {
return "The dex file size is out of range";
}
end = dex + fileSize;
const uint8_t* end = dex + fileSize;
// The class's type index, so that the class_def_item and the field ids can be filtered by class.
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";
}
uint32_t typeIndex = UINT32_MAX;
for (uint32_t i = 0; i < typeCount; ++i) {
const char* descriptor = dexString(dex, end, dexU32(types + (size_t) i * 4));
if (descriptor != nullptr && strcmp(descriptor, classDescriptor) == 0) {
typeIndex = i;
break;
}
}
if (typeIndex == UINT32_MAX) {
return "The class is not in the dex";
}
const uint8_t* classes = dex + dexU32(dex + FF_DEX_OFF_CLASS_DEFS);
const uint32_t classCount = dexU32(dex + FF_DEX_OFF_CLASS_DEFS_SIZE);
@@ -212,11 +263,75 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
return "The dex class table is out of range";
}
for (uint32_t i = 0; i < classCount; ++i) {
const uint8_t* classDef = classes + (size_t) i * FF_DEX_CLASS_DEF_SIZE;
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);
@@ -258,20 +373,40 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
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 && strcmp(name, fieldName) == 0) {
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. Reported here rather than on the first non-int in the list, because the
// list legitimately holds strings (`DESCRIPTOR`) and booleans.
return "The dex static field is not an int";
// 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;
}
*result = value;
return nullptr;
*requests[r].result = value;
settled[r] = 1;
}
}
return "The field is not a static field of the class";
// 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;
}
return "The class has no class_def_item";
// 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;
}
// ---------------------------------------------------------------------------------------------
@@ -279,59 +414,114 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
// ---------------------------------------------------------------------------------------------
typedef struct FFDexMapping {
uint8_t* mapped; // the jar, as mapped; nullptr once released
uint8_t* mapped; // the jar, as mapped; nullptr once released
size_t mappedSize;
const uint8_t* data; // the dex bytes: inside `mapped`, or `inflated`
const uint8_t* data; // the dex bytes, inside `mapped`
size_t size;
uint8_t* inflated; // owned; only set when the entry had to be decompressed
} FFDexMapping;
static void wrapDexMapping(FFDexMapping* mapping) {
assert(mapping);
free(mapping->inflated);
if (mapping->mapped != nullptr) {
munmap(mapping->mapped, mapping->mappedSize);
}
}
// The local file header of `classes.dex` carries both the payload bounds and its compression, so a
// single walk over the jar is enough to find it either way. AOSP builds framework jars with the
// entry STORED so that ART can map it, which is the common case and needs no decompression.
static const char* findDexEntry(const uint8_t* jar, size_t jarSize, const uint8_t** data, size_t* dataSize, uint32_t* uncompressedSize, uint16_t* method) {
for (size_t i = 0; i + FF_ZIP_LOCAL_HEADER_SIZE <= jarSize; ++i) {
if (memcmp(jar + i, "PK\x03\x04", 4) != 0) {
continue;
// 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;
}
const uint16_t nameLength = dexU16(jar + i + FF_ZIP_OFF_NAME_LENGTH);
const uint16_t extraLength = dexU16(jar + i + FF_ZIP_OFF_EXTRA_LENGTH);
if (nameLength != sizeof(FF_DEX_ENTRY) - 1) {
continue;
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;
}
const size_t headerSize = FF_ZIP_LOCAL_HEADER_SIZE + (size_t) nameLength + (size_t) extraLength;
if (headerSize > jarSize - i) {
continue;
}
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";
}
if (memcmp(jar + i + FF_ZIP_LOCAL_HEADER_SIZE, FF_DEX_ENTRY, sizeof(FF_DEX_ENTRY) - 1) != 0) {
continue;
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";
}
const uint32_t compressedSize = dexU32(jar + i + FF_ZIP_OFF_COMPRESSED_SIZE);
if (compressedSize == 0 || compressedSize > jarSize - i - headerSize) {
// A zero size means a data descriptor follows the payload instead of the header; the
// magic scan in the caller still covers that case as long as the entry is STORED.
continue;
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;
}
*method = dexU16(jar + i + FF_ZIP_OFF_METHOD);
*uncompressedSize = dexU32(jar + i + FF_ZIP_OFF_UNCOMPRESSED_SIZE);
*data = jar + i + headerSize;
*dataSize = compressedSize;
return nullptr;
offset += entrySize;
}
return "The jar has no usable classes.dex entry";
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.
// 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) {
@@ -358,8 +548,11 @@ static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t unc
FF_LIBRARY_LOAD_SYMBOL(zlib, inflate, "dlsym(inflate) failed")
FF_LIBRARY_LOAD_SYMBOL(zlib, inflateEnd, "dlsym(inflateEnd) failed")
uint8_t* buffer = malloc(uncompressedSize);
// 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";
}
@@ -372,75 +565,190 @@ static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t unc
stream.next_out = buffer;
stream.avail_out = (uInt) uncompressedSize;
if (ffinflateInit2_(&stream, -MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream)) != Z_OK) {
free(buffer);
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) {
free(buffer);
return "Inflating classes.dex failed";
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* ffDexStaticInt(const char* jarPath, const char* classDescriptor, const char* fieldName, int32_t* result) {
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 = {};
const int fd = open(jarPath, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return "open(jar) failed";
}
struct stat st = {};
if (fstat(fd, &st) != 0 || st.st_size <= 0) {
close(fd);
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.
close(fd);
if (mapping.mapped == MAP_FAILED) {
mapping.mapped = nullptr;
return "mmap(jar) failed";
}
const uint8_t* data = nullptr;
size_t dataSize = 0;
uint32_t uncompressedSize = 0;
uint16_t method = FF_ZIP_METHOD_STORED;
if (findDexEntry(mapping.mapped, mapping.mappedSize, &data, &dataSize, &uncompressedSize, &method) != nullptr) {
data = findDexMagic(mapping.mapped, mapping.mappedSize, &dataSize);
if (data == nullptr) {
return "No dex in the jar";
{
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";
}
} else if (method == FF_ZIP_METHOD_DEFLATED) {
#ifdef FF_HAVE_ZLIB
const char* error = inflateDex(data, dataSize, uncompressedSize, &mapping.inflated);
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;
}
data = mapping.inflated;
dataSize = uncompressedSize;
#else
return "The jar deflates classes.dex and fastfetch was built without zlib";
#endif
} else if (method != FF_ZIP_METHOD_STORED) {
return "classes.dex 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;
return dexStaticInt(mapping.data, mapping.size, classDescriptor, fieldName, result);
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;
}
+38 -4
View File
@@ -24,7 +24,41 @@
#include "fastfetch.h"
// Resolves the int value of `<classDescriptor>.<fieldName>` out of the `classes.dex` of `jarPath`.
// `classDescriptor` is the dex type descriptor, e.g. "Landroid/net/wifi/IWifiManager$Stub;".
// Returns nullptr on success, a static message otherwise.
[[gnu::nonnull(1, 2, 3, 4), nodiscard]] const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, const char* fieldName, int32_t* result);
// 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);
+1 -2
View File
@@ -1,7 +1,6 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#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
+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";
}
+12 -28
View File
@@ -1,38 +1,22 @@
#pragma once
#include "fastfetch.h"
#include "common/time.h"
[[gnu::nonnull(1), gnu::pure, 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;
}
#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
+42 -42
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) {
@@ -280,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);
+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";
}
+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
-183
View File
@@ -280,183 +280,6 @@ 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;
}
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";
}
qjsData.ctx = qjsData.ffJS_NewContext(qjsData.rt);
if (qjsData.ctx == nullptr) {
qjsData.ffJS_FreeRuntime(qjsData.rt);
qjsData.rt = nullptr;
return "JS_NewContext() failed";
}
libqjs = nullptr; // don't close quickjs
return nullptr;
}
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
// 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) {
@@ -813,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
-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
@@ -295,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);
+17 -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"
@@ -41,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;
}
}
@@ -146,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);
@@ -165,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
@@ -175,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";
}
}
@@ -191,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";
}
@@ -204,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";
}
+7 -2
View File
@@ -441,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";
}
@@ -471,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";
}
@@ -490,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) {
+14
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"
@@ -87,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);
@@ -97,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";
}
}
@@ -273,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";
}
@@ -281,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";
}
@@ -303,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";
}
@@ -329,6 +337,8 @@ 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;
@@ -355,6 +365,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
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 "
@@ -371,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";
}
@@ -417,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);
+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
-190
View File
@@ -1,197 +1,7 @@
#include "fastfetch.h"
#include "common/library.h"
#include "common/networking.h"
#include "common/strutil.h"
#include "common/debug.h"
#ifdef FF_HAVE_ZLIB
#include <zlib.h>
struct FFZlibLibrary {
FF_LIBRARY_SYMBOL(inflateInit2_)
FF_LIBRARY_SYMBOL(inflate)
FF_LIBRARY_SYMBOL(inflateEnd)
bool inited;
} zlibData;
const char* ffNetworkingLoadZlibLibrary(void) {
if (!zlibData.inited) {
zlibData.inited = true;
FF_LIBRARY_LOAD_MESSAGE(zlib,
#ifdef _WIN32
"zlib1"
#else
"libz"
#endif
FF_LIBRARY_EXTENSION,
2)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd)
zlib = nullptr; // don't auto dlclose
}
return zlibData.ffinflateEnd == nullptr ? "Failed to load libz" : nullptr;
}
// Try to pre-read gzip header to determine uncompressed size
static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize) {
// gzip file format: http://www.zlib.org/rfc-gzip.html
if (dataSize < 10 || ((const uint8_t*) data)[0] != 0x1f || ((const uint8_t*) data)[1] != 0x8b) {
return 0;
}
// Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB
if (dataSize > 18) {
// Get ISIZE value from the end of file (little endian)
const uint8_t* tail = (const uint8_t*) data + dataSize - 4;
uint32_t uncompressedSize = (uint32_t) tail[0] | ((uint32_t) tail[1] << 8u) | ((uint32_t) tail[2] << 16u) | ((uint32_t) tail[3] << 24u);
// For valid gzip files, this value is the length of the uncompressed data modulo 2^32
if (uncompressedSize > 0) {
FF_DEBUG("Read uncompressed size from GZIP trailer: %u bytes", uncompressedSize);
// Add some margin to the estimated size for safety
return uncompressedSize + 64;
}
}
// If unable to get size from trailer or size is 0, use estimated value
// Typically, text data compression ratio is between 3-5x, we use the larger value
uint32_t estimatedSize = dataSize * 5;
FF_DEBUG("Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes", estimatedSize);
return estimatedSize;
}
// Decompress gzip content
bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) {
// `headerEnd` itself is non-null per the `nonnull(2)` contract; what it points to is not expressible as an attribute
assert(*headerEnd == '\r');
// Calculate header size
uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars);
*headerEnd = '\0'; // Replace delimiter with null character for easier processing
// Ensure Content-Encoding is in response headers, not in response body
bool hasGzip = strcasestr(buffer->chars, "\nContent-Encoding: gzip") != nullptr;
*headerEnd = '\r'; // Restore delimiter
if (!hasGzip) {
FF_DEBUG("No gzip compressed content detected, skipping decompression");
return true;
}
FF_DEBUG("Gzip compressed content detected, preparing for decompression");
const char* bodyStart = headerEnd + 4; // Skip delimiter
if (buffer->length <= headerSize + 4) {
// No content to decompress
FF_DEBUG("Compressed content size is 0, skipping decompression");
return true;
}
// Calculate compressed content size
uint32_t compressedSize = buffer->length - headerSize - 4;
// Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b)
if (compressedSize < 2 || (uint8_t) bodyStart[0] != 0x1f || (uint8_t) bodyStart[1] != 0x8b) {
FF_DEBUG("Content is not valid gzip format, skipping decompression");
return false;
}
// Predict uncompressed size
uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize);
// Create decompression buffer with estimated size
FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5);
FF_DEBUG("Created decompression buffer: %u bytes", decompressedBuffer.allocated);
// Initialize decompression
z_stream zs = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.avail_in = (uInt) compressedSize,
.next_in = (Bytef*) bodyStart,
.avail_out = (uInt) ffStrbufGetFree(&decompressedBuffer),
.next_out = (Bytef*) decompressedBuffer.chars,
};
// Initialize decompression engine
if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream)) != Z_OK) {
FF_DEBUG("Failed to initialize decompression engine");
return false;
}
uInt availableOut = zs.avail_out;
// Perform decompression
int result = zlibData.ffinflate(&zs, Z_FINISH);
// If output buffer is insufficient, try to extend buffer
while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0)) {
FF_DEBUG("Output buffer insufficient, trying to extend");
// Save already decompressed data amount
uint32_t alreadyDecompressed = (uint32_t) (availableOut - zs.avail_out);
decompressedBuffer.length += alreadyDecompressed;
decompressedBuffer.chars[decompressedBuffer.length] = '\0';
ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2);
// Set output parameters to point to new buffer
zs.avail_out = (uInt) ffStrbufGetFree(&decompressedBuffer);
zs.next_out = (Bytef*) (decompressedBuffer.chars + decompressedBuffer.length);
availableOut = zs.avail_out;
// Decompress again
result = zlibData.ffinflate(&zs, Z_FINISH);
}
// Check for decompression errors before using result
if (result != Z_STREAM_END) {
FF_DEBUG("Decompression failed with zlib error: %d", result);
zlibData.ffinflateEnd(&zs);
return false;
}
zlibData.ffinflateEnd(&zs);
// Calculate decompressed size (from the last inflate call)
uint32_t decompressedSize = (uint32_t) (availableOut - zs.avail_out);
decompressedBuffer.length += decompressedSize;
decompressedBuffer.chars[decompressedBuffer.length] = '\0';
FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length);
// Modify Content-Length header and remove Content-Encoding header
// Use decompressedBuffer.length (total) not decompressedSize (last chunk only)
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedBuffer.length + 64);
char* line = nullptr;
size_t len = 0;
while (ffStrbufGetline(&line, &len, buffer)) {
if (ffStrStartsWithIgnCase(line, "Content-Encoding:")) {
continue;
} else if (ffStrStartsWithIgnCase(line, "Content-Length:")) {
ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n", decompressedBuffer.length);
continue;
} else if (line[0] == '\r') {
ffStrbufAppendS(&newBuffer, "\r\n");
ffStrbufGetlineRestore(&line, &len, buffer);
break;
}
ffStrbufAppendS(&newBuffer, line); // Including the trailing \r
ffStrbufAppendC(&newBuffer, '\n');
}
ffStrbufAppend(&newBuffer, &decompressedBuffer);
ffStrbufDestroy(buffer);
ffStrbufInitMove(buffer, &newBuffer);
return true;
}
#endif // FF_HAVE_ZLIB
const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen) {
uint32_t nameLen = (uint32_t) strlen(name);
uint32_t pos = 0;
+2 -39
View File
@@ -146,6 +146,7 @@ static const char* tryNonThreadingFastPath(FFNetworkingState* state) {
#endif
#else
FF_UNUSED(state);
FF_DEBUG("TFO support is not available");
return "TFO support is not available";
#endif
}
@@ -213,12 +214,6 @@ static const char* initNetworkingState(FFNetworkingState* state, const char* hos
}
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly tell the server we don't need to keep the connection
// If compression needs to be enabled
if (state->compression) {
FF_DEBUG("Enabling HTTP content compression");
ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
}
ffStrbufAppendS(&state->command, headers);
ffStrbufAppendS(&state->command, "\r\n");
@@ -330,26 +325,6 @@ error:
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
if (state->compression) {
FF_DEBUG("Compression enabled, checking if zlib is available");
#ifdef FF_HAVE_ZLIB
const char* zlibError = ffNetworkingLoadZlibLibrary();
// Only enable compression if zlib library is successfully loaded
if (zlibError == nullptr) {
FF_DEBUG("Successfully loaded zlib library, compression enabled");
} else {
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
state->compression = false;
}
#else
FF_DEBUG("zlib not supported at build time, compression disabled");
state->compression = false;
#endif
} else {
FF_DEBUG("Compression disabled");
}
const char* initResult = initNetworkingState(state, host, port, path, headers);
if (initResult != nullptr) {
FF_DEBUG("Initialization failed: %s", initResult);
@@ -593,6 +568,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
}
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
FF_DEBUG("Failed to decode chunked response");
return "Failed to decode chunked response";
}
@@ -610,18 +586,5 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "Content length mismatch";
}
// If compression was used, try to decompress
#ifdef FF_HAVE_ZLIB
if (state->compression) {
FF_DEBUG("Content received, checking if compressed");
if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {
FF_DEBUG("Decompression failed or invalid compression format");
return "Failed to decompress or invalid format";
} else {
FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
}
}
#endif
return nullptr;
}
+16 -49
View File
@@ -14,8 +14,9 @@ static LPFN_CONNECTEX ConnectEx;
static const char* initWsaData(WSADATA* wsaData) {
FF_DEBUG("Initializing WinSock");
if (WSAStartup(MAKEWORD(2, 2), wsaData) != 0) {
FF_DEBUG("WSAStartup() failed");
int ret = WSAStartup(MAKEWORD(2, 2), wsaData);
if (ret != 0) {
FF_DEBUG("WSAStartup() failed: %s", ffDebugWin32Error((DWORD) ret));
return "WSAStartup() failed";
}
@@ -28,7 +29,7 @@ static const char* initWsaData(WSADATA* wsaData) {
// Dummy socket needed for WSAIoctl
SOCKET sockfd = WSASocketW(AF_INET, SOCK_STREAM, 0, nullptr, 0, 0);
if (sockfd == INVALID_SOCKET) {
FF_DEBUG("WSASocketW(AF_INET, SOCK_STREAM) failed");
FF_DEBUG("WSASocketW(AF_INET, SOCK_STREAM) failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError()));
WSACleanup();
return "WSASocketW(AF_INET, SOCK_STREAM) failed";
}
@@ -36,7 +37,7 @@ static const char* initWsaData(WSADATA* wsaData) {
DWORD dwBytes;
GUID guid = WSAID_CONNECTEX;
if (WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), &dwBytes, nullptr, nullptr) != 0) {
FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed");
FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError()));
closesocket(sockfd);
WSACleanup();
return "WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed";
@@ -51,24 +52,6 @@ static const char* initWsaData(WSADATA* wsaData) {
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
if (state->compression) {
#ifdef FF_HAVE_ZLIB
const char* zlibError = ffNetworkingLoadZlibLibrary();
// Only enable compression if zlib library is successfully loaded
if (zlibError == nullptr) {
FF_DEBUG("Successfully loaded zlib library, compression enabled");
} else {
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
state->compression = false;
}
#else
FF_DEBUG("zlib not supported at build time, compression disabled");
state->compression = false;
#endif
} else {
FF_DEBUG("Compression disabled");
}
static WSADATA wsaData;
if (wsaData.wVersion == 0) {
const char* error = initWsaData(&wsaData);
@@ -90,8 +73,9 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
};
wchar_t hostW[256];
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(hostW, (ULONG) sizeof(hostW), nullptr, host, (ULONG) strlen(host) + 1))) {
FF_DEBUG("Failed to convert host to wide string: %s", host);
NTSTATUS status = RtlUTF8ToUnicodeN(hostW, (ULONG) sizeof(hostW), nullptr, host, (ULONG) strlen(host) + 1);
if (!NT_SUCCESS(status)) {
FF_DEBUG("Failed to convert host to wide string: %s: %s", host, ffDebugNtStatus(status));
return "Failed to convert host to wide string";
}
@@ -99,14 +83,15 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
_itow(port, portW, 10);
FF_DEBUG("Resolving address: %s:%u (%s)", host, port, state->ipv6 ? "IPv6" : "IPv4");
if (GetAddrInfoW(hostW, portW, &hints, &addr) != 0) {
FF_DEBUG("GetAddrInfoW() failed");
int ret = GetAddrInfoW(hostW, portW, &hints, &addr);
if (ret != 0) {
FF_DEBUG("GetAddrInfoW() failed: %s", ffDebugWin32Error((DWORD) ret));
return "GetAddrInfoW() failed";
}
state->sockfd = WSASocketW(addr->ai_family, addr->ai_socktype, addr->ai_protocol, nullptr, 0, 0);
if (state->sockfd == INVALID_SOCKET) {
FF_DEBUG("WSASocketW() failed");
FF_DEBUG("WSASocketW() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError()));
FreeAddrInfoW(addr);
return "WSASocketW() failed";
}
@@ -149,8 +134,9 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
// Initialize overlapped structure with WSA event for asynchronous I/O
state->overlapped = (OVERLAPPED) {};
if (!NT_SUCCESS(NtCreateEvent(&state->overlapped.hEvent, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE))) {
FF_DEBUG("NtCreateEvent() failed");
NTSTATUS eventStatus = NtCreateEvent(&state->overlapped.hEvent, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE);
if (!NT_SUCCESS(eventStatus)) {
FF_DEBUG("NtCreateEvent() failed: %s", ffDebugNtStatus(eventStatus));
closesocket(state->sockfd);
FreeAddrInfoW(addr);
state->sockfd = INVALID_SOCKET;
@@ -176,13 +162,6 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
ffStrbufAppendF(&state->command, ":%u", port);
}
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly request connection closure
// Add compression support if enabled
if (state->compression) {
FF_DEBUG("Enabling HTTP content compression");
ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
}
ffStrbufAppendS(&state->command, headers);
ffStrbufAppendS(&state->command, "\r\n");
@@ -417,6 +396,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
}
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
FF_DEBUG("Failed to decode chunked response");
return "Failed to decode chunked response";
}
@@ -436,18 +416,5 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "Content length mismatch";
}
// If compression was used, try to decompress
#ifdef FF_HAVE_ZLIB
if (state->compression) {
FF_DEBUG("Content received, checking if compressed");
if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {
FF_DEBUG("Decompression failed or invalid compression format");
return "Failed to decompress or invalid format";
} else {
FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
}
}
#endif
return nullptr;
}
+4
View File
@@ -1,4 +1,5 @@
#include "common/path.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/arrutil.h"
@@ -6,6 +7,7 @@
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
char* path = getenv("PATH");
if (!path) {
FF_DEBUG("The PATH environment variable is not set");
return "$PATH not set";
}
@@ -50,6 +52,7 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
return nullptr;
}
ffStrbufClear(result);
FF_DEBUG("%s is not executable in any of the %s entries", name, "PATH");
return "Executable not found";
}
#else
@@ -62,6 +65,7 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
char buffer[MAX_PATH + 1];
DWORD length = SearchPathA(nullptr, name, ".exe", sizeof(buffer), buffer, nullptr);
if (length == 0) {
FF_DEBUG("SearchPathA(%s) failed: %s", name, ffDebugWin32Error(GetLastError()));
ffStrbufClear(result);
return "Executable not found";
}
+6
View File
@@ -1,4 +1,5 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/percent.h"
#include "common/color.h"
#include "common/option.h"
@@ -27,6 +28,8 @@ const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFl
yyjson_arr_foreach (jsonVal, idx, max, item) {
const char* flag = yyjson_get_str(item);
if (!flag) {
FF_DEBUG("A percent.type array entry is of JSON type %s, not a string",
yyjson_get_type_desc(item));
return "Error: percent.type: invalid flag string";
}
if (ffStrEqualsIgnCase(flag, "num")) {
@@ -40,6 +43,7 @@ const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFl
} else if (ffStrEqualsIgnCase(flag, "bar-monochrome")) {
flags |= FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT;
} else {
FF_DEBUG("Unknown percent.type flag \"%s\"", flag);
return "Error: percent.type: unknown flag string";
}
}
@@ -48,6 +52,7 @@ const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFl
return nullptr;
}
FF_DEBUG("percent.type is of JSON type %s", yyjson_get_type_desc(jsonVal));
return "Error: usage: percent.type must be a number or an array of strings";
}
@@ -285,6 +290,7 @@ bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageMo
return true;
}
[[gnu::cold]]
void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config) {
yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, module, "percent");
yyjson_mut_obj_add_uint(doc, percent, "green", config.green);
+35 -13
View File
@@ -1,4 +1,5 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/processing.h"
#include "common/io.h"
#include "common/strutil.h"
@@ -42,23 +43,30 @@ extern char** environ;
enum { FF_PIPE_BUFSIZ = 8192 };
static inline int ffPipe2(int* fds, int flags) {
static inline int ffPipe2(int* readEnd, int* writeEnd, int flags) {
int fds[2];
#ifndef FF_HAVE_PIPE2
if (pipe(fds) == -1) {
return -1;
}
fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | flags);
fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | flags);
return 0;
#else
return pipe2(fds, flags);
if (pipe2(fds, flags) == -1) {
return -1;
}
#endif
*readEnd = fds[0];
*writeEnd = fds[1];
return 0;
}
// Not thread-safe
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
int pipes[2];
if (ffPipe2(pipes, O_CLOEXEC) == -1) {
FF_AUTO_CLOSE_FD int pipeRead = -1;
FF_AUTO_CLOSE_FD int pipeWrite = -1;
if (ffPipe2(&pipeRead, &pipeWrite, O_CLOEXEC) == -1) {
FF_DEBUG("pipe() failed: %s", strerror(errno));
return "pipe() failed";
}
@@ -75,7 +83,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
posix_spawn_file_actions_t file_actions;
posix_spawn_file_actions_init(&file_actions);
posix_spawn_file_actions_adddup2(&file_actions, pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&file_actions, pipeWrite, useStdErr ? STDERR_FILENO : STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&file_actions, nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);
if (ffIsValidNativeFD(stdinFd)) {
posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO);
@@ -123,8 +131,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
posix_spawn_file_actions_destroy(&file_actions);
if (ret != 0) {
close(pipes[0]);
close(pipes[1]);
FF_DEBUG("posix_spawnp() failed: %s", strerror(ret));
if (ret == ENOENT) {
return "command not found";
}
@@ -136,14 +143,13 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
// https://github.com/termux/termux-packages/issues/25369
childPid = fork();
if (childPid == -1) {
close(pipes[0]);
close(pipes[1]);
FF_DEBUG("fork() failed: %s", strerror(errno));
return "fork() failed";
}
if (childPid == 0) {
// Child process
dup2(pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO);
dup2(pipeWrite, useStdErr ? STDERR_FILENO : STDOUT_FILENO);
dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);
if (ffIsValidNativeFD(stdinFd)) {
dup2(stdinFd, STDIN_FILENO);
@@ -155,9 +161,9 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
#endif
close(pipes[1]);
outHandle->pid = childPid;
outHandle->pipeRead = pipes[0];
outHandle->pipeRead = pipeRead;
pipeRead = -1; // ownership moved into outHandle, which ffProcessReadOutput() lets go
return nullptr;
}
@@ -177,6 +183,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
struct pollfd pollfd = { childPipeFd, POLLIN, 0 };
int pollret = poll(&pollfd, 1, timeout);
if (pollret == 0) {
FF_DEBUG("poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)");
kill(childPid, SIGTERM);
waitpid(childPid, nullptr, 0);
return "poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)";
@@ -199,6 +206,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
return "child process exited abnormally";
}
if (WEXITSTATUS(stat_loc) == 127) {
FF_DEBUG("command not found");
return "command not found";
}
// We only handle 127 as an error. See `getTerminalVersionUrxvt` in `terminalshell.c`
@@ -210,6 +218,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
}
}
FF_DEBUG("read(childPipeFd, str, FF_PIPE_BUFSIZ) failed: %s", strerror(errno));
return "read(childPipeFd, str, FF_PIPE_BUFSIZ) failed";
}
@@ -475,6 +484,7 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons
const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty) {
if (pid <= 0) {
FF_DEBUG("Invalid pid");
return "Invalid pid";
}
@@ -489,6 +499,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
char buf[PROC_FILE_BUFFSIZ];
ssize_t nRead = ffReadFileData(procFilePath, sizeof(buf) - 1, buf);
if (nRead <= 8) {
FF_DEBUG("ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed");
return "ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed";
}
buf[nRead] = '\0'; // pid (comm) state ppid pgrp session tty
@@ -499,16 +510,19 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
// comm in `/proc/pid/stat` is not encoded, and may contain ' ', ')' or even `\n`
const char* start = memchr(buf, '(', (size_t) nRead);
if (!start) {
FF_DEBUG("memchr(stat, '(') failed");
return "memchr(stat, '(') failed";
}
start++;
const char* end = memrchr(start, ')', (size_t) nRead - (size_t) (start - buf));
if (!end) {
FF_DEBUG("memrchr(stat, ')') failed");
return "memrchr(stat, ')') failed";
}
ffStrbufSetNS(name, (uint32_t) (end - start), start);
ffStrbufTrimRightSpace(name);
if (name->chars[0] == '\0') {
FF_DEBUG("process name is empty");
return "process name is empty";
}
pState = end + 2; // skip ") "
@@ -520,6 +534,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
{
int ppid_, tty_;
if (sscanf(pState + 2, "%d %*d %*d %d", &ppid_, &tty_) < 2) {
FF_DEBUG("sscanf(stat) failed");
return "sscanf(stat) failed";
}
@@ -536,6 +551,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
snprintf(procFilePath, sizeof(procFilePath), "/proc/%d/comm", (int) pid);
ssize_t nRead = ffReadFileBuffer(procFilePath, name);
if (nRead <= 0) {
FF_DEBUG("ffReadFileBuffer(/proc/pid/comm, name) failed");
return "ffReadFileBuffer(/proc/pid/comm, name) failed";
}
ffStrbufTrimRightSpace(name);
@@ -548,6 +564,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
size_t size = sizeof(proc);
if (sysctl(
(int[]) { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }, 4, &proc, &size, nullptr, 0)) {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -574,6 +591,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
size_t size = sizeof(proc);
if (sysctl(
(int[]) { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }, 4, &proc, &size, nullptr, 0)) {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -600,6 +618,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
size_t size = sizeof(proc);
if (sysctl(
(int[]) { CTL_KERN, KERN_PROC2, KERN_PROC_PID, pid, sizeof(proc), 1 }, 6, &proc, &size, nullptr, 0) != 0) {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -625,6 +644,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
snprintf(path, sizeof(path), "/proc/%d/psinfo", (int) pid);
psinfo_t proc;
if (ffReadFileData(path, sizeof(proc), &proc) != sizeof(proc)) {
FF_DEBUG("ffReadFileData(psinfo) failed");
return "ffReadFileData(psinfo) failed";
}
@@ -650,6 +670,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
*tty = (int) proc.p_tdev;
}
} else {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -667,6 +688,7 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
#else
FF_DEBUG("Unsupported platform");
return "Unsupported platform";
#endif
+18 -4
View File
@@ -1,4 +1,5 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/processing.h"
#include "common/io.h"
@@ -100,6 +101,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
0,
nullptr);
if (hChildPipeRead == INVALID_HANDLE_VALUE) {
FF_DEBUG("CreateNamedPipeW() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateNamedPipeW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
}
@@ -116,6 +118,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
0,
nullptr);
if (hChildPipeWrite == INVALID_HANDLE_VALUE) {
FF_DEBUG("CreateFileW() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
}
@@ -141,7 +144,9 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
argvToCmdline(argv, &buf);
uint32_t cmdlineBytes = (buf.length + 1) * sizeof(wchar_t);
cmdline = malloc(cmdlineBytes);
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, nullptr, buf.chars, buf.length + 1))) {
NTSTATUS status = RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, nullptr, buf.chars, buf.length + 1);
if (!NT_SUCCESS(status)) {
FF_DEBUG("RtlUTF8ToUnicodeN() failed: %s", ffDebugNtStatus(status));
return "RtlUTF8ToUnicodeN() failed";
}
}
@@ -165,6 +170,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return "command not found";
}
FF_DEBUG("CreateProcessW() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateProcessW() failed";
}
@@ -197,7 +203,9 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
handle->pid = INVALID_HANDLE_VALUE;
handle->pipeRead = INVALID_HANDLE_VALUE;
if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, nullptr, SynchronizationEvent, FALSE))) {
NTSTATUS createStatus = STATUS_SUCCESS;
if (timeout >= 0 && !NT_SUCCESS(createStatus = NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, nullptr, SynchronizationEvent, FALSE))) {
FF_DEBUG("NtCreateEvent() failed: %s", ffDebugNtStatus(createStatus));
return "NtCreateEvent() failed";
}
@@ -216,7 +224,8 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
nullptr,
nullptr);
if (status == STATUS_PENDING) {
switch (NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 })) {
NTSTATUS waitStatus = NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 });
switch (waitStatus) {
case STATUS_WAIT_0:
status = iosb.Status;
break;
@@ -227,6 +236,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
}
default:
FF_DEBUG("NtWaitForSingleObject(hReadEvent) failed: %s", ffDebugNtStatus(waitStatus));
terminateChildProcess(hProcess, hChildPipeRead, hReadEvent, &iosb);
return "NtWaitForSingleObject(hReadEvent) failed";
}
@@ -237,6 +247,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
}
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtReadFile(hChildPipeRead) failed: %s", ffDebugNtStatus(status));
terminateChildProcess(hProcess, hChildPipeRead, nullptr, &iosb);
return "NtReadFile(hChildPipeRead) failed";
}
@@ -248,12 +259,15 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
exit: {
PROCESS_BASIC_INFORMATION info = {};
ULONG size;
if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) {
NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size);
if (NT_SUCCESS(status)) {
assert(size == sizeof(info));
if (info.ExitStatus != STILL_ACTIVE && info.ExitStatus != 0) {
FF_DEBUG("Child process exited with an error");
return "Child process exited with an error";
}
} else {
FF_DEBUG("NtQueryInformationProcess(ProcessBasicInformation) failed: %s", ffDebugNtStatus(status));
return "NtQueryInformationProcess(ProcessBasicInformation) failed";
}
}
+7
View File
@@ -1,17 +1,22 @@
#include "common/sysctl.h"
#include "common/debug.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef __OpenBSD__
const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) {
size_t neededLength;
if (sysctl((int[]) { mib1, mib2 }, 2, nullptr, &neededLength, nullptr, 0) != 0 || neededLength == 1) { // neededLength is 1 for empty strings, because of the null terminator
FF_DEBUG("sysctl({%d, %d}) length query failed: %s", mib1, mib2, strerror(errno));
return "sysctl() length query failed";
}
ffStrbufEnsureFree(result, (uint32_t) neededLength - 1);
if (sysctl((int[]) { mib1, mib2 }, 2, result->chars + result->length, &neededLength, nullptr, 0) != 0) {
FF_DEBUG("sysctl({%d, %d}) failed to retrieve %zu bytes: %s", mib1, mib2, neededLength, strerror(errno));
return "sysctl() failed to retrieve string data";
}
@@ -62,12 +67,14 @@ int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) {
const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
size_t neededLength;
if (sysctlbyname(propName, nullptr, &neededLength, nullptr, 0) != 0 || neededLength == 1) { // neededLength is 1 for empty strings, because of the null terminator
FF_DEBUG("sysctlbyname(%s) length query failed: %s", propName, strerror(errno));
return "sysctlbyname() failed";
}
ffStrbufEnsureFree(result, (uint32_t) neededLength - 1);
if (sysctlbyname(propName, result->chars + result->length, &neededLength, nullptr, 0) != 0) {
FF_DEBUG("sysctlbyname(%s) failed to retrieve %zu bytes: %s", propName, neededLength, strerror(errno));
return "sysctlbyname() failed to retrieve string data";
}
+1
View File
@@ -154,6 +154,7 @@ bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, F
return true;
}
[[gnu::cold]]
void ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config) {
if (!temp) {
yyjson_mut_obj_add_bool(doc, module, "temp", false);
-1
View File
@@ -25,7 +25,6 @@ typedef struct FFNetworkingState {
FFstrbuf command;
uint32_t timeout;
bool ipv6;
bool compression; // if true, HTTP content compression will be enabled if supported
bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails
} FFNetworkingState;
+18 -17
View File
@@ -21,23 +21,24 @@ typedef struct FFModuleFormatArgList {
typedef struct FFModuleDisplayName {
const char* en; // English
const char* ar; // Arabic
const char* cs; // Czech
const char* de; // German
const char* es; // Spanish
const char* fr; // French
const char* gl; // Galician
const char* he; // Hebrew
const char* id; // Indonesian
const char* it; // Italian
const char* ja; // Japanese
const char* ko; // Korean
const char* pl; // Polish
const char* pt; // (Brazilian) Portuguese
const char* ru; // Russian
const char* tr; // Turkish
const char* uk; // Ukrainian
const char* vi; // Vietnamese
const char* ar; // Arabic
const char* cs; // Czech
const char* de; // German
const char* es; // Spanish
const char* fi; // Finnish
const char* fr; // French
const char* gl; // Galician
const char* he; // Hebrew
const char* id; // Indonesian
const char* it; // Italian
const char* ja; // Japanese
const char* ko; // Korean
const char* pl; // Polish
const char* pt; // (Brazilian) Portuguese
const char* ru; // Russian
const char* tr; // Turkish
const char* uk; // Ukrainian
const char* vi; // Vietnamese
const char* zh_CN; // Simplified Chinese
const char* zh_TW; // Traditional Chinese
} FFModuleDisplayName;
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "common/FFstrbuf.h"
#include "fastfetch.h"
// Not `pure`: it trims the trailing spaces of `value` before reporting whether it is set
[[gnu::nonnull(1), nodiscard]] bool ffIsSmbiosValueSet(FFstrbuf* value);
+32 -8
View File
@@ -1,10 +1,34 @@
#!/bin/sh
# Convert logo.svg to logo.ico
rsvg-convert -w 16 -h 16 logo.svg > logo16.png
rsvg-convert -w 32 -h 32 logo.svg > logo32.png
rsvg-convert -w 48 -h 48 logo.svg > logo48.png
rsvg-convert -w 64 -h 64 logo.svg > logo64.png
rsvg-convert -w 128 -h 128 logo.svg > logo128.png
rsvg-convert -w 256 -h 256 logo.svg > logo256.png
convert logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png logo.ico
# Convert logo.svg to logo.ico.
#
# Every frame is stored as PNG. ImageMagick's ICO writer only uses PNG for frames that are
# 256x256 or larger and falls back to an uncompressed DIB for everything smaller, which
# inflates the icon from ~19 KB to ~108 KB (and to ~370 KB on ImageMagick 7, which also
# stores 256x256 as a DIB). Packing the frames here keeps the icon small and predictable.
set -e
cd "$(dirname "$0")"
for size in 16 32 48 64 128 256; do
rsvg-convert -w "$size" -h "$size" ../../logo/logo.svg > "logo$size.png"
done
python3 - <<'PY'
import struct
sizes = [16, 32, 48, 64, 128, 256]
frames = [open(f"logo{s}.png", "rb").read() for s in sizes]
header = struct.pack("<HHH", 0, 1, len(sizes))
offset = 6 + 16 * len(sizes)
entries = b""
for size, frame in zip(sizes, frames):
dim = 0 if size == 256 else size # 0 means 256 in the ICO directory
entries += struct.pack("<BBBBHHII", dim, dim, 0, 0, 1, 32, len(frame), offset)
offset += len(frame)
with open("logo.ico", "wb") as f:
f.write(header + entries + b"".join(frames))
PY
rm logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png
+8
View File
@@ -1,4 +1,5 @@
#include "com.h"
#include "common/debug.h"
#include <stdlib.h>
@@ -14,15 +15,19 @@ static const char* doInitCom() {
if (FAILED(res)) {
switch (res) {
case E_INVALIDARG:
FF_DEBUG("RoInitialize() failed: invalid argument: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: invalid argument";
case E_OUTOFMEMORY:
FF_DEBUG("RoInitialize() failed: out of memory: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: out of memory";
case E_UNEXPECTED:
FF_DEBUG("RoInitialize() failed: unexpected error: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: unexpected error";
case RPC_E_CHANGED_MODE:
// COM was already initialized with a different concurrency model
return nullptr;
default:
FF_DEBUG("RoInitialize() failed: unknown error: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: unknown error";
}
}
@@ -42,13 +47,16 @@ static const char* doInitCom() {
if (FAILED(res)) {
switch (res) {
case E_INVALIDARG:
FF_DEBUG("CoInitializeEx() failed: invalid argument: 0x%08lX (%s)", res, ffDebugHResult(res));
return "CoInitializeEx() failed: invalid argument";
case E_OUTOFMEMORY:
FF_DEBUG("CoInitializeEx() failed: out of memory: 0x%08lX (%s)", res, ffDebugHResult(res));
return "CoInitializeEx() failed: out of memory";
case RPC_E_CHANGED_MODE:
// COM was already initialized with a different concurrency model
return nullptr;
default:
FF_DEBUG("CoInitializeEx() failed: unknown error: 0x%08lX (%s)", res, ffDebugHResult(res));
return "CoInitializeEx() failed: unknown error";
}
}
+237
View File
@@ -0,0 +1,237 @@
#include "folders.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/windows/nt.h"
#include "common/windows/registry.h"
#include "common/windows/unicode.h"
#include <ntstatus.h>
// Where the shell keeps the values SHGetKnownFolderPath() would compute.
//
// `User Shell Folders` is the store folder redirection writes to -- HKCU for the user's own
// folders, HKLM for the machine-wide ones -- and it holds the *unexpanded* form, which is why the
// environment expansion below is not optional. `ProfileList\<SID>\ProfileImagePath` is what the
// logon session recorded as the profile directory, and is read by ffGetProfilePath().
#define FF_FOLDERS_HKCU_SHELL_FOLDERS L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
#define FF_FOLDERS_HKLM_SHELL_FOLDERS L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
#define FF_FOLDERS_HKLM_CURRENT_VERSION L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion"
#define FF_FOLDERS_HKLM_PROFILE_LIST L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
// The environment fallbacks. Two of the registry values above are self-referential -- `Common
// AppData` is literally `%ProgramData%` -- so these are the same answer, not a different one.
#define FF_FOLDERS_ENV_LOCAL_APP_DATA L"%LOCALAPPDATA%"
#define FF_FOLDERS_ENV_ROAMING_APP_DATA L"%APPDATA%"
#define FF_FOLDERS_ENV_PROGRAM_DATA L"%ProgramData%"
#define FF_FOLDERS_ENV_PROGRAM_FILES L"%ProgramFiles%"
// Every folder is resolved once and then kept. ffGetKnownFolderPath() is called about a dozen times
// per process -- getCacheDir, getConfigDirs and getDataDirs all ask for the same folders -- and
// each answer is a pure function of the registry and this process' environment. `states` separates
// "not looked up yet" from "looked up and not there", which the path itself cannot: an empty
// FFstrbuf is a valid answer.
//
// No lock guards this: the only thread fastfetch ever creates is the networking module's
// asynchronous connect, and no module that resolves a known folder runs on it.
typedef enum FFFolderState : uint8_t {
FF_FOLDER_UNRESOLVED,
FF_FOLDER_FOUND,
FF_FOLDER_MISSING,
} FFFolderState;
static FFstrbuf folderPaths[FF_KNOWN_FOLDER_COUNT];
static FFFolderState folderStates[FF_KNOWN_FOLDER_COUNT];
// Expands `%NAME%` placeholders from this process' environment block.
//
// The work is done in UTF-16 so that a user name outside the ANSI code page survives: getenv()
// goes through the CRT's narrow copy of the environment and would mangle it.
//
// RtlExpandEnvironmentStrings() substitutes nothing and still reports success for a variable it
// cannot find (measured), so a leftover '%' is treated as a failure. A directory name that
// legitimately contains one is a false negative, and the only thing that costs is the caller's
// fallback.
static bool expandEnv(const wchar_t* source, uint32_t sourceLen, FFstrbuf* result) {
// Asking for the required length is done with a null destination, which the call reports as
// STATUS_BUFFER_TOO_SMALL -- a *failure* status that still fills in `size`. The status is
// therefore deliberately not checked here; a call that writes nothing leaves `size` at zero.
SIZE_T size = 0; // in characters, including the null terminator
(void) RtlExpandEnvironmentStrings(nullptr, source, sourceLen, nullptr, 0, &size);
if (size == 0) {
return false;
}
FF_AUTO_FREE wchar_t* expanded = (wchar_t*) malloc(size * sizeof(wchar_t));
SIZE_T written = 0;
if (!NT_SUCCESS(RtlExpandEnvironmentStrings(nullptr, source, sourceLen, expanded, size, &written)) || written == 0) {
return false;
}
if (wcschr(expanded, L'%')) {
return false;
}
ffStrbufSetNWS(result, (uint32_t) (written - 1), expanded);
return true;
}
static bool expandEnvZ(const wchar_t* source, FFstrbuf* result) {
return expandEnv(source, (uint32_t) wcslen(source), result);
}
// `raw` is the payload of a REG_SZ / REG_EXPAND_SZ value: UTF-16, usually null terminated.
static bool expandBuffer(const FFArgBuffer* raw, FFstrbuf* result) {
uint32_t length = raw->length / sizeof(wchar_t);
if (length == 0) {
return false;
}
const wchar_t* valueW = (const wchar_t*) raw->data;
if (valueW[length - 1] == L'\0') {
--length;
}
return length > 0 && expandEnv(valueW, length, result);
}
// FF_ARG_TYPE_BUFFER rather than FF_ARG_TYPE_STRBUF, because the value has to stay UTF-16 for
// expandEnv(); the strbuf flavour converts it to UTF-8 on the way out. The buffer flavour does not
// report the registry type either, so REG_SZ and REG_EXPAND_SZ are not told apart -- expanding a
// string without placeholders is a no-op, which is what makes that acceptable.
static bool readExpandedValue(HANDLE hKey, const wchar_t* valueNameW, FFstrbuf* result) {
FFArgBuffer raw = {};
if (!ffRegReadData(hKey, valueNameW, &raw, nullptr)) {
return false;
}
FF_AUTO_FREE void* data = raw.data;
return expandBuffer(&raw, result);
}
static bool readExpandedValueFrom(HKEY hRootKey, const wchar_t* subKeyW, const wchar_t* valueNameW, FFstrbuf* result) {
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
if (!ffRegOpenKeyForRead(hRootKey, subKeyW, &hKey, nullptr)) {
return false;
}
return readExpandedValue(hKey, valueNameW, result);
}
// The SID comes from the caller rather than from a token query here: FFPlatform_windows.c already
// resolves it for `platform->sid`, and the two are the same SID by definition.
bool ffGetProfilePath(const wchar_t* sidW, FFstrbuf* result) {
assert(sidW);
FF_AUTO_CLOSE_FD HANDLE hProfileList = nullptr;
if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_PROFILE_LIST, &hProfileList, nullptr)) {
FF_DEBUG("Cannot open %ls", FF_FOLDERS_HKLM_PROFILE_LIST);
return false;
}
FF_AUTO_CLOSE_FD HANDLE hProfile = nullptr;
if (!ffRegOpenSubkeyForRead(hProfileList, sidW, &hProfile, nullptr)) {
FF_DEBUG("No profile is registered for %ls", sidW);
return false;
}
return readExpandedValue(hProfile, L"ProfileImagePath", result);
}
// `Local AppData` and `AppData` share a key, so they are read together.
//
// The names have to be exact, spaces and all: `Local AppData` is `Local`, one space, `AppData` --
// there is no `Local App Data`, and asking for it fails the whole batch below.
static const struct {
FFKnownFolder folder;
const wchar_t* valueNameW;
const wchar_t* envW;
} shellFolderEntries[] = {
{ FF_KNOWN_FOLDER_LOCAL_APP_DATA, L"Local AppData", FF_FOLDERS_ENV_LOCAL_APP_DATA },
{ FF_KNOWN_FOLDER_ROAMING_APP_DATA, L"AppData", FF_FOLDERS_ENV_ROAMING_APP_DATA },
};
// One open plus one NtQueryMultipleValueKey resolves both -- and whichever of the two is asked for
// first caches the pair, which is what the callers want anyway.
//
// NtQueryMultipleValueKey is all-or-nothing: one absent name fails the batch, which would push
// *both* folders to the environment fallback. That is survivable, because these values are
// themselves just `%LOCALAPPDATA%` and `%APPDATA%` expanded -- but it is also invisible, and an
// invisible fallback is how a wrong value name went unnoticed here. So a failed batch is retried
// one value at a time, and the extra queries are only paid when the batch has already failed.
static void resolveUserShellFolders(void) {
FFArgBuffer buffers[ARRAY_SIZE(shellFolderEntries)] = {};
FFRegValueArg args[ARRAY_SIZE(shellFolderEntries)];
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
args[i] = (FFRegValueArg) { .type = FF_ARG_TYPE_BUFFER, .value = &buffers[i], .name = shellFolderEntries[i].valueNameW };
}
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, FF_FOLDERS_HKCU_SHELL_FOLDERS, &hKey, nullptr)) {
if (ffRegReadValues(hKey, ARRAY_SIZE(shellFolderEntries), args, nullptr)) {
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
if (expandBuffer(&buffers[i], &folderPaths[shellFolderEntries[i].folder])) {
folderStates[shellFolderEntries[i].folder] = FF_FOLDER_FOUND;
}
}
} else {
FF_DEBUG("Batched read of %ls failed; retrying one value at a time", FF_FOLDERS_HKCU_SHELL_FOLDERS);
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
if (readExpandedValue(hKey, shellFolderEntries[i].valueNameW, &folderPaths[shellFolderEntries[i].folder])) {
folderStates[shellFolderEntries[i].folder] = FF_FOLDER_FOUND;
}
}
}
}
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
// Every buffer has to be freed even when the read failed part way through, which is why the
// free is not tied to the success path above.
free(buffers[i].data);
FFKnownFolder folder = shellFolderEntries[i].folder;
if (folderStates[folder] == FF_FOLDER_UNRESOLVED &&
expandEnvZ(shellFolderEntries[i].envW, &folderPaths[folder])) {
folderStates[folder] = FF_FOLDER_FOUND;
}
if (folderStates[folder] == FF_FOLDER_UNRESOLVED) {
folderStates[folder] = FF_FOLDER_MISSING;
}
}
}
static bool resolveFolder(FFKnownFolder folder) {
switch (folder) {
case FF_KNOWN_FOLDER_LOCAL_APP_DATA:
case FF_KNOWN_FOLDER_ROAMING_APP_DATA:
resolveUserShellFolders(); // Resolves both of them
return folderStates[folder] == FF_FOLDER_FOUND;
case FF_KNOWN_FOLDER_PROGRAM_DATA:
return readExpandedValueFrom(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_SHELL_FOLDERS, L"Common AppData", &folderPaths[folder]) ||
expandEnvZ(FF_FOLDERS_ENV_PROGRAM_DATA, &folderPaths[folder]);
case FF_KNOWN_FOLDER_PROGRAM_FILES:
return readExpandedValueFrom(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_CURRENT_VERSION, L"ProgramFilesDir", &folderPaths[folder]) ||
expandEnvZ(FF_FOLDERS_ENV_PROGRAM_FILES, &folderPaths[folder]);
default:
assert(false);
return false;
}
}
bool ffGetKnownFolderPath(FFKnownFolder folder, FFstrbuf* result) {
assert(result);
assert(folder < FF_KNOWN_FOLDER_COUNT);
if (folderStates[folder] == FF_FOLDER_UNRESOLVED) {
folderStates[folder] = resolveFolder(folder) ? FF_FOLDER_FOUND : FF_FOLDER_MISSING;
}
if (folderStates[folder] != FF_FOLDER_FOUND) {
return false;
}
ffStrbufSet(result, &folderPaths[folder]);
return true;
}
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include "fastfetch.h"
#include "common/io.h"
#include <wchar.h> // ffGetProfilePath() takes a SID as a wide string
// The known folders fastfetch needs. They are spelled out instead of taking a REFKNOWNFOLDERID so
// that this header does not have to drag in <shlobj.h> -- the call sites include it for reasons of
// their own, and a header that only works when <windows.h> came first is a trap.
typedef enum FFKnownFolder : uint8_t {
FF_KNOWN_FOLDER_LOCAL_APP_DATA,
FF_KNOWN_FOLDER_ROAMING_APP_DATA,
FF_KNOWN_FOLDER_PROGRAM_DATA,
FF_KNOWN_FOLDER_PROGRAM_FILES,
FF_KNOWN_FOLDER_COUNT,
} FFKnownFolder;
// Resolves a known folder without initializing the shell.
//
// The first SHGetKnownFolderPath() call in a process costs ~2.4 ms (measured on Windows 10 22H2):
// the shell's known-folder machinery is built on demand, and whichever folder is asked for first
// pays for it. Every later call is free, so a process that needs a handful of folders pays that
// price once and can never amortize it. Each folder below is instead one registry value or one
// environment variable away -- ~0.03 ms for all of them together -- so the shell is never touched.
//
// The answer is cached: these five folders are asked for about a dozen times per process, and each
// answer is a pure function of the registry and this process' environment. That is safe without a
// lock, because nothing that resolves a known folder ever runs on the one thread fastfetch creates.
//
// Returns false, leaving `result` untouched, when the value is missing or unusable, so that the
// caller keeps whatever fallback it had for the shell API failing. The path uses backslashes,
// exactly like SHGetKnownFolderPath().
bool ffGetKnownFolderPath(FFKnownFolder folder, FFstrbuf* result);
// Resolves the profile directory recorded for `sidW`, a SID string as produced by
// RtlConvertSidToUnicodeString().
//
// There is no FF_KNOWN_FOLDER_PROFILE because the caller already has the SID:
// FFPlatform_windows.c resolves it once for `platform->sid`, and deriving it a second time here
// would mean a second NtQueryInformationToken(). The answer is deliberately not cached -- this is
// called once per process, and from one place.
//
// Returns false, leaving `result` untouched, when the value is missing or unusable, so that the
// caller keeps whatever fallback it had for the shell API failing. The path uses backslashes,
// exactly like SHGetKnownFolderPath().
bool ffGetProfilePath(const wchar_t* sidW, FFstrbuf* result);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 19 KiB

-1
View File
@@ -1 +0,0 @@
../../logo/logo.svg
-3
View File
@@ -171,9 +171,6 @@ NTSTATUS NTAPI NtQuerySystemEnvironmentValueEx(
_Out_opt_ PULONG Attributes // EFI_VARIABLE_*
);
NTSTATUS NTAPI RtlGUIDFromString(IN PCUNICODE_STRING GuidString, OUT GUID* Guid);
NTSTATUS NTAPI RtlStringFromGUIDEx(IN const GUID* Guid, OUT PUNICODE_STRING GuidString, IN BOOLEAN AllocateGuidString);
typedef struct _SYSTEM_SECUREBOOT_INFORMATION {
BOOLEAN SecureBootEnabled;
BOOLEAN SecureBootCapable;
+456 -215
View File
@@ -2,35 +2,119 @@
#include "battery.h"
#include "common/android/api.h"
#include "common/android/binder.h"
#include "common/android/dex.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/processing.h"
#include "common/properties.h"
#include "common/settings.h"
#include "common/strutil.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string.h>
// Android exposes the battery through BatteryManager, which the NDK does not wrap and whose sysfs
// backing is unreadable from an app UID. What does work without any permission is
// android.os.IBatteryPropertiesRegistrar in system_server, reachable either over /dev/binder (see
// common/android/binder.h) or through a `dumpsys battery` subprocess. The binder route is the cheap
// one: ~0.3 ms for both properties, and no child process.
// backing is unreadable from an app UID -- /sys/class/power_supply is closed to untrusted_app
// outright, directory and all. What does work without any permission is
// android.os.IBatteryPropertiesRegistrar in system_server, over /dev/binder (see
// common/android/binder.h).
//
// The registrar only answers properties that do not need BATTERY_STATS, and that rules out
// everything the module would otherwise like to show. Manufacturer, model name, technology, serial
// number, manufacture date, temperature and cycle count all sit behind that permission.
// `dumpsys battery` is the other route, tried first for shell and root because it is the richer of
// the two: a flat `key: value` list read by name, so it carries what neither the registrar nor a
// property has -- the technology and the critical capacity level -- and neither a vendor that prints
// extra keys in the middle nor a release that appends a field can break it. The binder reply is
// positional and breaks silently instead. What it costs is a fork/exec, which is why it is only
// reached where it can succeed; see the UID gate in ffDetectBattery.
//
// An app UID gets none of that, and not because Android withholds it. `ACTION_BATTERY_CHANGED` is a
// sticky broadcast, and a sticky broadcast is read without a permission: it carries the technology,
// and a cycle count since Android 14 -- the device's own BatteryManager declares `EXTRA_CYCLE_COUNT`,
// spelled "android.os.extra.CYCLE_COUNT". What is out of reach is the call that fetches it. A sticky
// broadcast is read by `registerReceiver`, the NDK does not wrap it, and a native binary has no Java
// side to make that call from, so every route to it is a Java process. termux-api is one, and it is
// not the answer: on the device this was written on it hung on six of six runs, each killed at 30
// seconds having written nothing, after exec'ing `am broadcast` and waiting on a socket that has no
// timeout of its own.
//
// What is left within reach is a pair of system properties BatteryService mirrors out of every update
// it processes: `debug.tracing.plug_type` and `debug.tracing.battery_status`. A system property is
// world readable, which is the whole point of them here -- the charger bits used to need `dumpsys
// battery`, and so the shell UID, while the status used to cost a second transaction. Both are now
// free, to any UID; see the two defines below.
//
// The temperature comes from neither service. The kernel publishes it as a thermal zone, and
// /sys/class/thermal is readable from an app UID -- unlike /sys/class/power_supply, which is closed
// outright, directory and all -- so it is read there whenever `temp` asks for it. The dump has a
// `temperature` line of its own and that one wins where the dump runs; see `detectTemperature` below
// for why the zone is looked up by name rather than addressed by number.
//
// The registrar answers nothing else. `BatteryProperty` has no temperature, no technology and no
// cycle count, and manufacturer and model name are not battery data at all. Of the properties that
// do exist, ids 7 to 12 -- the ones Android 15 added -- are behind BATTERY_STATS, and ids 1 to 6 are
// not. The one reading the module wants that is not a property either, how long the charge will
// last, comes from a different service; see the `batterystats` block below.
// The transaction code and the reply layout are both positional, and neither is negotiated with the
// service, so each only stays correct as long as AOSP keeps the order it already has. The code has
// not: `IBatteryPropertiesRegistrar` still declared registerListener / unregisterListener /
// getProperty up to Android 9, and Android 10 dropped the two listener methods, which moved
// `getProperty` from the third position to the first. The layout has, because `BatteryProperty` has
// only ever grown at the end -- API 35 appended [string8 mValueString] to it -- so the field read
// below is still the first one written.
// The reply layout is positional and is not negotiated with the service, so it only stays correct as
// long as AOSP keeps the order it already has. It has, because `BatteryProperty` has only ever grown
// at the end -- API 35 appended [string8 mValueString] to it -- so the field read below is still the
// first one written.
//
// The transaction code is positional for the same reason, and it has *not* stayed: the interface
// still declared registerListener / unregisterListener / getProperty up to Android 9, and Android 10
// dropped the two listener methods, which moved `getProperty` from the third position to the first.
// The number is therefore read out of the device's own jar rather than guessed from the API level,
// because a vendor fork is free to insert a method ahead of it -- and a wrong code reaches a
// different method, or none. See common/android/dex.h.
//
// Getting either wrong is quiet rather than loud: `ffBinderReadU64` returns 0 for a read past the
// end of the reply, so a drift shows up as a wrong capacity, not as a failed call.
#define FF_BATTERY_ANDROID_SERVICE "batteryproperties"
#define FF_BATTERY_ANDROID_DESCRIPTOR "android.os.IBatteryPropertiesRegistrar"
// IBatteryPropertiesRegistrar has always been part of framework.jar -- it is not one of the
// frameworks that moved into an APEX -- so there is one path and no fallback. The method this module
// calls is named by the constant the dex carries for it, which is what the lookup turns into whatever
// number this build uses.
#define FF_BATTERY_ANDROID_JAR "/system/framework/framework.jar"
#define FF_BATTERY_ANDROID_STUB "Landroid/os/IBatteryPropertiesRegistrar$Stub;"
#define FF_BATTERY_ANDROID_GET_PROPERTY "TRANSACTION_getProperty"
// How long the charge will last is not a property of the pack: it is a forecast BatteryStatsService
// makes from the discharge history, so it lives on a second service. That service is `batterystats`,
// whose interface is com.android.internal.app.IBatteryStats, and it is also in framework.jar --
// com.android.internal.app is not one of the namespaces that moved into an APEX -- so the same read
// resolves this code as the one above.
//
// The interface has 120 methods and the dex cannot say which of them an app UID may call: neither
// IBatteryStats nor its $Stub carries an `_enforcePermission` wrapper, so the wall is inside
// BatteryStatsService, which lives in services.jar and is not available to read. It was therefore
// measured, as uid 10478 on an Android 16 device. The three this module needs answer with no
// permission at all -- and so do `getAllWakeLocks` (117) and `getAllWakeLocksEx` (118), which is why
// the claim is not "only three of 120". Every read-only statistic getter tried (`getAwakeTimeBattery`,
// `getAwakeTimePlugged`, and the cellular / wifi / gps / bluetooth ones) came back with
// "Access denied, requires: android.permission.BATTERY_STATS", or an `anyOf` of that and
// UPDATE_DEVICE_STATS.
//
// The code is read out of the jar for the same reason as `getProperty`, and the numbers make the
// point better than the argument does: this method is transaction 19 on that Android 16 device, 22
// on a Mi 10 (Android 14) and 20 on a Mi 9 (Android 10). A hardcoded 19 would reach a different
// method on two of the three.
#define FF_BATTERY_ANDROID_STATS_SERVICE "batterystats"
#define FF_BATTERY_ANDROID_STATS_DESCRIPTOR "com.android.internal.app.IBatteryStats"
#define FF_BATTERY_ANDROID_STATS_STUB "Lcom/android/internal/app/IBatteryStats$Stub;"
#define FF_BATTERY_ANDROID_COMPUTE_BATTERY_TIME_REMAINING "TRANSACTION_computeBatteryTimeRemaining"
// The temperature is a third source again, and not a service this time: the kernel publishes it as a
// thermal zone, and the zone Android's own thermal HAL names `battery` is the one to read. The name
// is what is matched -- not the number, and not a prefix -- for the reasons spelled out in
// detectTemperature below.
#define FF_BATTERY_ANDROID_THERMAL_DIR "/sys/class/thermal/"
#define FF_BATTERY_ANDROID_THERMAL_ZONE_PREFIX "thermal_zone"
#define FF_BATTERY_ANDROID_THERMAL_BATTERY_TYPE "battery"
// BatteryManager.BATTERY_PROPERTY_*
typedef enum FFBatteryAndroidProperty : uint32_t {
FF_BATTERY_ANDROID_PROPERTY_CAPACITY = 4,
@@ -38,19 +122,49 @@ typedef enum FFBatteryAndroidProperty : uint32_t {
} FFBatteryAndroidProperty;
// BatteryManager.BATTERY_STATUS_*, which the health HAL's `BatteryStatus` mirrors value for value.
// Only the three the module has something to say about are carried: NOT_CHARGING (4) and FULL (5)
// both mean a charger is attached, which the powered bits already say.
// The module has something to say about only three of the five: NOT_CHARGING (4) and FULL (5) both
// mean a charger is attached, and it answers nothing about the charger to put that on, while the dump
// route maps UNKNOWN (1) as well, because there it is a different statement from the service not
// answering at all. The two are named anyway, because the property route validates the whole range
// before it trusts a value it did not get from the service.
typedef enum FFBatteryAndroidStatus : uint32_t {
FF_BATTERY_ANDROID_STATUS_UNKNOWN = 1,
FF_BATTERY_ANDROID_STATUS_CHARGING = 2,
FF_BATTERY_ANDROID_STATUS_DISCHARGING = 3,
FF_BATTERY_ANDROID_STATUS_NOT_CHARGING = 4,
FF_BATTERY_ANDROID_STATUS_FULL = 5,
} FFBatteryAndroidStatus;
// The pair of system properties that stand in for two of the readings above. Both are published by
// `BatteryService.processValuesLocked` -- the two strings sit next to each other in the constant pool
// of classes.dex in this device's services.jar, which is what a pair of `SystemProperties.set` calls
// looks like after the fact -- and neither is a `BatteryProperty`, so nothing about the registrar's
// positional reply applies to them.
//
// debug.tracing.plug_type -- BatteryManager.BATTERY_PLUGGED_*: 1 AC, 2 USB, 4 wireless,
// 8 dock, and 0 when nothing is attached. The dock bit has no
// `FF_BATTERY_STATUS_*` counterpart and is masked off below.
// debug.tracing.battery_status -- BatteryManager.BATTERY_STATUS_*, the value
// `getProperty(BATTERY_PROPERTY_STATUS)` answers with as well:
// 1 UNKNOWN, 2 CHARGING, 3 DISCHARGING, 4 NOT_CHARGING, 5 FULL.
#define FF_BATTERY_ANDROID_PROP_PLUG_TYPE "debug.tracing.plug_type"
#define FF_BATTERY_ANDROID_PROP_BATTERY_STATUS "debug.tracing.battery_status"
// The encoding of plug_type, read out of the device's own framework.jar rather than assumed: the
// values are bits of one field and not a sequence, so `value != 0` is not a test for "wired", and a
// device reporting the dock would be told it is on neither AC nor USB nor wireless.
typedef enum FFBatteryAndroidPlugged : uint32_t {
FF_BATTERY_ANDROID_PLUGGED_AC = 1,
FF_BATTERY_ANDROID_PLUGGED_USB = 2,
FF_BATTERY_ANDROID_PLUGGED_WIRELESS = 4,
} FFBatteryAndroidPlugged;
// BatteryCapacityLevel.BATTERY_CAPACITY_LEVEL_CRITICAL, the level the framework itself shuts the
// device down on. This is the health HAL's numbering, not the one the older
// `BatteryManager.BATTERY_CAPACITY_LEVEL_*` constants used, which had CRITICAL at 4. The rest of the
// enum -- -1 UNSUPPORTED, 0 UNKNOWN, 2 LOW, 3 NORMAL, 4 HIGH, 5 FULL -- only restates the
// percentage, so only this value is carried.
// enum is skipped: -1 (UNSUPPORTED) and 0 (UNKNOWN) are what a HAL that does not implement the
// property reports, and 2 to 5 only restate the percentage. Only the dump carries this -- it is not a
// `BatteryProperty`, so the binder route never sees it.
typedef enum FFBatteryAndroidCapacityLevel : int32_t {
FF_BATTERY_ANDROID_CAPACITY_LEVEL_CRITICAL = 1,
} FFBatteryAndroidCapacityLevel;
@@ -62,6 +176,11 @@ typedef enum FFBatteryAndroidCapacityLevel : int32_t {
#define FF_BATTERY_ANDROID_VALUE_OFFSET 12
#define FF_BATTERY_ANDROID_VALUE_UNSET 0x8000000000000000ULL // Long.MIN_VALUE, i.e. never filled in
// A method that answers with a bare `long` writes [int32 exception][int64 value] and nothing else:
// there is no out parameter, so no non-null marker sits in front of the value the way it does in a
// `getProperty` reply. Twelve bytes in all on the device.
#define FF_BATTERY_ANDROID_LONG_VALUE_OFFSET 4
// One `getProperty` reply is [exception code][return value][value present][int64 mValueLong], and it
// measures 24 bytes on the device: API 35 appended [string8 mValueString] to `BatteryProperty`, and a
// reply carries a word for it even when it is empty. A dump of the first 32 covers every word.
@@ -103,15 +222,8 @@ static void debugDumpReply(const uint8_t* data, size_t size) {
#define FF_BATTERY_ANDROID_DEBUG_DUMP(data, size) ((void) 0)
#endif
static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t property, uint64_t* value) {
// `getProperty` is the third method declared in `IBatteryPropertiesRegistrar` up to Android 9 and
// the first one from Android 10 on. Written as an `if` because clang rejects `__builtin_available`
// -- which is what FF_ANDROID_API_AT_LEAST expands to -- in any other position.
uint32_t transaction = 3u;
if (FF_ANDROID_API_AT_LEAST(29)) {
transaction = 1u;
}
FF_DEBUG("Property %u goes out as transaction %u (3 up to Android 9, 1 from Android 10)", property, transaction);
static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t transaction, uint32_t property, uint64_t* value) {
FF_DEBUG("Property %u goes out as transaction %u", property, transaction);
uint8_t parcelBuffer[128];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
@@ -164,9 +276,277 @@ static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t prope
return nullptr;
}
static const char* parseBinder(FFlist* results) {
// Reads either of the two properties above as a number. Both are written by `Integer.toString`, so a
// buffer whose text is not a number is the property saying nothing rather than a 0 -- 0 is a plug type,
// and it is not a status.
static bool readPropertyUInt(const char* name, uint32_t* value) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
if (!ffSettingsGetAndroidProperty(name, &buffer)) {
FF_DEBUG("The \"%s\" property is not set, so nothing is read from it", name);
return false;
}
// `ffStrbufToUInt` answers the default for text that does not start with a digit, which is why the
// default is a value neither property can hold rather than a 0.
const uint64_t parsed = ffStrbufToUInt(&buffer, UINT64_MAX);
if (parsed == UINT64_MAX) {
FF_DEBUG("The \"%s\" property reads \"%s\", which is not a number", name, buffer.chars);
return false;
}
*value = (uint32_t) parsed;
return true;
}
// The charger type. This is what the property route has and the registrar does not: `getProperty`
// answers nothing about the charger, so the three bits used to come from `dumpsys battery`, i.e. from
// the shell UID and root alone. A device that does not publish the property leaves the bits clear,
// which is also what an unplugged charger looks like -- the module cannot tell those apart, and neither
// can the framework, whose sticky broadcast carries this same value as `EXTRA_PLUGGED`.
static void fillPlugged(FFBatteryResult* battery) {
uint32_t plugged = 0;
if (!readPropertyUInt(FF_BATTERY_ANDROID_PROP_PLUG_TYPE, &plugged)) {
return;
}
// A mask rather than a comparison: the three are bits of one field, and it is the bits that have a
// `FF_BATTERY_STATUS_*` counterpart. BATTERY_PLUGGED_DOCK (8) is the one that does not, so a device
// on a dock reports no powered bit at all -- and `plugged != 0` would have called that "wired".
if (plugged & FF_BATTERY_ANDROID_PLUGGED_AC) {
battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
}
if (plugged & FF_BATTERY_ANDROID_PLUGGED_USB) {
battery->status |= FF_BATTERY_STATUS_USB_CONNECTED;
}
if (plugged & FF_BATTERY_ANDROID_PLUGGED_WIRELESS) {
battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED;
}
FF_DEBUG("The \"%s\" property is %u, which becomes status bits 0x%x (1 AC, 2 USB, 4 wireless; 0 is "
"what both an unplugged charger and a device that does not publish the property look like)",
FF_BATTERY_ANDROID_PROP_PLUG_TYPE, plugged, (unsigned) battery->status);
}
// The status, read the cheap way first. The property carries the same `mHealthInfo.batteryStatus` the
// transaction would fetch, so the two can only differ inside the window between the health HAL reporting
// a value and the service storing it, while what the property costs is a read of a shared page instead
// of a round trip through /dev/binder -- a round trip measured at about 0.04 ms on the device this was
// written on, and what it saves is a positional reply that has to be decoded as well.
//
// It stays a preference rather than a replacement: a property that is not set (an early boot, or a
// build whose BatteryService does not publish it) or that does not carry one of the five values sends
// the caller to the service, which is the one answer that cannot be stale.
static bool detectStatusFromProperty(uint64_t* status) {
uint32_t value = 0;
if (!readPropertyUInt(FF_BATTERY_ANDROID_PROP_BATTERY_STATUS, &value)) {
return false;
}
if (value < FF_BATTERY_ANDROID_STATUS_UNKNOWN || value > FF_BATTERY_ANDROID_STATUS_FULL) {
FF_DEBUG("The \"%s\" property is %u, which is not a battery status, so the service is asked",
FF_BATTERY_ANDROID_PROP_BATTERY_STATUS, value);
return false;
}
*status = value;
return true;
}
// Calls a method that takes no argument and answers with one `long`, which is how
// IBatteryStats.computeBatteryTimeRemaining() is declared. `transaction` comes out of the jar, and the
// sentinel the reader leaves for a code it could not find is caught here, so that a build which does
// not declare the method is a message rather than a transaction that means something else.
//
// A negative answer is not an error: it is how the interface spells "no estimate", so it is passed
// back as it came and the caller decides what that is worth. The unit is milliseconds.
static const char* getTimeRemaining(FFBinder* binder, uint32_t handle, int32_t transaction, int64_t* milliseconds) {
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("computeBatteryTimeRemaining is unresolved in the jar (transaction %d)", (int) transaction);
return "The battery stats service does not declare computeBatteryTimeRemaining";
}
uint8_t parcelBuffer[128];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
ffBinderParcelPutInterfaceToken(&parcel, FF_BATTERY_ANDROID_STATS_DESCRIPTOR);
uint8_t replyBuffer[128];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
if (error != nullptr) {
FF_DEBUG("computeBatteryTimeRemaining could not be transacted: %s", error);
return error;
}
if (ffBinderReplyIsStatus(&reply)) {
FF_DEBUG("computeBatteryTimeRemaining came back as a status reply of %d instead of a parcel",
(int) ffBinderReadI32(reply.data, reply.size, 0));
return "The battery stats service rejected the request";
}
// The exception word is read first and the value only once it is known to be a value: a denied
// call answers [exception -1][string16 message], so the eight bytes at the value's offset are
// then the length and the first characters of the message rather than a number.
const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
if (exception != 0) {
FF_DEBUG("computeBatteryTimeRemaining is transaction %d, reply %zu bytes, exception %d",
transaction, reply.size, exception);
return "The battery stats service raised an exception";
}
*milliseconds = (int64_t) ffBinderReadU64(reply.data, reply.size, FF_BATTERY_ANDROID_LONG_VALUE_OFFSET);
FF_DEBUG("computeBatteryTimeRemaining is transaction %d: reply is %zu bytes, value %" PRId64 " ms",
transaction, reply.size, *milliseconds);
return nullptr;
}
// Writes the time estimate onto the battery just reported. It is not one of the registrar's
// properties -- it is a forecast BatteryStatsService makes -- so it comes from a second service and
// is filled in here, after the capacity and the status are already in place.
//
// `binder` and `handle` are the caller's, already open on `batterystats`. Nothing here can fail the
// module: an estimate that is not available is what `FFBatteryResult` already spells with -1.
static void fillTimeRemaining(FFBinder* binder, uint32_t handle, int32_t transaction, FFlist* results) {
if (results->length == 0) {
FF_DEBUG("No time remaining is reported: no battery was reported to put it on");
return;
}
int64_t milliseconds = 0;
const char* error = getTimeRemaining(binder, handle, transaction, &milliseconds);
if (error != nullptr) {
FF_DEBUG("The time remaining could not be computed, so none is reported: %s", error);
return;
}
if (milliseconds <= 0) {
FF_DEBUG("The battery stats service answered %" PRId64 " ms, which is how it says it has no estimate",
milliseconds);
return;
}
// The interface answers in milliseconds and `FFBatteryResult` carries seconds, which is what the
// other platform modules fill in. An answer under a second truncates to 0, which the module reads
// as "unknown" as well, so there is nothing to guard against.
FF_LIST_GET(FFBatteryResult, *results, 0)->timeRemaining = (int32_t) (milliseconds / 1000);
}
// The battery temperature is neither a registrar property nor part of the health HAL: the kernel
// publishes it as a thermal zone, and the zone Android's thermal HAL names `battery` is the one to
// read. It is read from /sys/class/thermal, which an app UID may open -- the reason the module does
// not simply read /sys/class/power_supply/battery/temp is that that directory is closed to it.
//
// The zone is *found*, not addressed. Its number is a property of the probe order rather than of the
// hardware -- the battery is `thermal_zone67` on the device this was written on, and nothing promises
// the next one -- so the directory is walked and each entry is matched on the `type` it reports.
//
// That match is an exact string, and has to stay one. A prefix would be cheaper and wrong: the same
// directory holds hardware trip points (`cpu-hw-trip-0` reports 95000, a perfectly believable 95
// degrees), current and battery levels (`pmih010x-ibat-lvl0`, 0 or a small integer) and raw registers
// (`vbat`, in millivolts). None of those are a temperature, and every one of them would be printed as
// one. Reading a different zone is not a smaller error than reading none.
//
// A device that names its battery zone something else therefore reports no temperature rather than a
// wrong one, which is what FF_BATTERY_TEMP_UNSET already means.
static double detectTemperature(void) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir(FF_BATTERY_ANDROID_THERMAL_DIR);
if (dirp == nullptr) {
FF_DEBUG("Opening \"%s\" failed, so no temperature is reported: %s", FF_BATTERY_ANDROID_THERMAL_DIR, strerror(errno));
return FF_BATTERY_TEMP_UNSET;
}
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
struct dirent* entry;
while ((entry = readdir(dirp)) != nullptr) {
if (!ffStrStartsWith(entry->d_name, FF_BATTERY_ANDROID_THERMAL_ZONE_PREFIX)) {
continue;
}
FF_AUTO_CLOSE_FD int zone = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (zone < 0) {
continue;
}
// The type file ends with a newline, which the comparison below would otherwise never survive.
if (!ffReadFileBufferRelative(zone, "type", &buffer)) {
continue;
}
ffStrbufTrimRightSpace(&buffer);
if (!ffStrbufEqualS(&buffer, FF_BATTERY_ANDROID_THERMAL_BATTERY_TYPE)) {
continue;
}
if (!ffReadFileBufferRelative(zone, "temp", &buffer)) {
FF_DEBUG("The battery zone \"%s\" has no readable temperature", entry->d_name);
return FF_BATTERY_TEMP_UNSET;
}
// sysfs thermal publishes millidegrees Celsius. That is the one thing every zone here agrees
// on, even the ones whose reading is not a temperature at all.
const double value = ffStrbufToDouble(&buffer, FF_BATTERY_TEMP_UNSET);
if (value == FF_BATTERY_TEMP_UNSET) {
FF_DEBUG("The battery zone \"%s\" reports \"%s\", which is not a number", entry->d_name, buffer.chars);
return FF_BATTERY_TEMP_UNSET;
}
FF_DEBUG("The battery zone is \"%s\", reporting %.2f degrees", entry->d_name, value / 1000.);
return value / 1000.;
}
FF_DEBUG("No zone in \"%s\" is named \"%s\", so no temperature is reported",
FF_BATTERY_ANDROID_THERMAL_DIR, FF_BATTERY_ANDROID_THERMAL_BATTERY_TYPE);
return FF_BATTERY_TEMP_UNSET;
}
// Temperature is opt-in on every platform, and on Android that is not only about the display: the
// walk above touches a directory with a hundred-odd entries on the device this was written on, and
// none of that is paid for unless `temp` asks for it.
static void fillTemperature(FFBatteryOptions* options, FFlist* results) {
if (!options->temp) {
FF_DEBUG("Temperature is not enabled, so the thermal zones are not walked");
return;
}
if (results->length == 0) {
return;
}
const double temperature = detectTemperature();
if (temperature != FF_BATTERY_TEMP_UNSET) {
FF_LIST_GET(FFBatteryResult, *results, 0)->temperature = temperature;
}
}
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
// Resolved before the binder is opened, so that a jar which does not declare the method says so
// rather than the module failing later with a message about the transport. Both codes are asked
// for in one call, which is cheaper than two: the reader walks the jar's dex entries in order and
// stops once every request has been answered, so a second request only costs the walk to the
// entry that defines the second class. That walk is not free here, because the two classes are
// not in the same entry -- `IBatteryPropertiesRegistrar$Stub` is in classes3.dex and
// `IBatteryStats$Stub` in classes5.dex -- and on the device this was measured on it added 1.3 ms
// to a module that had been 3.3 ms, while the service lookup and the transaction behind it stayed
// inside the noise. The reader reports a code it cannot find by leaving the sentinel in the
// result and says nothing more -- what a missing code means for this module is decided here, not
// there.
//
// The two are not equally important. `getProperty` is what the module is made of, so a build
// without it has nothing to report; `computeBatteryTimeRemaining` only adds the time estimate,
// and a build without it still has the capacity and the status, so its sentinel is carried on to
// where the estimate is asked for instead of failing the route here.
int32_t transaction = FF_DEX_STATIC_INT_UNRESOLVED;
int32_t timeRemainingTransaction = FF_DEX_STATIC_INT_UNRESOLVED;
const FFDexStaticIntRequest requests[] = {
{ FF_BATTERY_ANDROID_STUB, FF_BATTERY_ANDROID_GET_PROPERTY, &transaction },
{ FF_BATTERY_ANDROID_STATS_STUB, FF_BATTERY_ANDROID_COMPUTE_BATTERY_TIME_REMAINING, &timeRemainingTransaction },
};
const char* error = ffDexStaticInts(FF_BATTERY_ANDROID_JAR, requests, ARRAY_SIZE(requests));
if (error != nullptr) {
FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_BATTERY_ANDROID_JAR, error);
return error;
}
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("getProperty is unresolved in the jar (transaction %d)", (int) transaction);
return "The battery service does not declare getProperty";
}
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
const char* error = ffBinderOpen(&binder);
error = ffBinderOpen(&binder);
if (error != nullptr) {
FF_DEBUG("Opening /dev/binder failed: %s", error);
return error;
@@ -179,20 +559,30 @@ static const char* parseBinder(FFlist* results) {
FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_BATTERY_ANDROID_SERVICE, error);
return error;
}
FF_DEBUG("The \"%s\" service is handle %u", FF_BATTERY_ANDROID_SERVICE, service.handle);
FF_DEBUG("The \"%s\" service is handle %u, getProperty is transaction %d", FF_BATTERY_ANDROID_SERVICE, service.handle, transaction);
uint64_t capacity = 0;
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
error = getProperty(&binder, service.handle, (uint32_t) transaction, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
if (error != nullptr) {
FF_DEBUG("The capacity is what failed, so no battery is reported at all");
return error;
}
// The status is asked of the property first, because that is where the second transaction on the
// service is saved. The service is still asked when the property has nothing to say, and a failure
// there is still fatal: the capacity and the status are what this route is made of, and a battery
// whose status is unknown is not a battery this route can describe.
uint64_t status = 0;
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
if (error != nullptr) {
FF_DEBUG("The status is what failed, so no battery is reported at all");
return error;
if (detectStatusFromProperty(&status)) {
FF_DEBUG("The status came from the \"%s\" property and is %" PRIu64 ", so the service is not asked "
"for it",
FF_BATTERY_ANDROID_PROP_BATTERY_STATUS, status);
} else {
error = getProperty(&binder, service.handle, (uint32_t) transaction, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
if (error != nullptr) {
FF_DEBUG("The status is what failed, so no battery is reported at all");
return error;
}
}
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
@@ -203,187 +593,38 @@ static const char* parseBinder(FFlist* results) {
} else if (status == FF_BATTERY_ANDROID_STATUS_DISCHARGING) {
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
}
// The charger type is the one reading this route has that the registrar does not, and it is a
// property as well: the three bits used to come from `dumpsys battery`, and so from the shell UID
// and root alone.
fillPlugged(battery);
// The temperature comes from outside this route entirely, so it is asked for here rather than
// inside it: nothing about the binder session above has any bearing on it, and a device without a
// zone by that name simply leaves the field at the sentinel.
fillTemperature(options, results);
// The time estimate is the one part of this route that is allowed to be missing: a jar without
// the code, a service that is not running and a service that answers -1 all leave the field at
// the -1 the module starts it with, which is how `FFBatteryResult` spells "unknown". It is asked
// for last for that reason -- a failure here cannot cost the capacity and the status that have
// already been read.
[[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle stats = { .binder = &binder };
error = ffBinderLookupService(&binder, FF_BATTERY_ANDROID_STATS_SERVICE, FF_BINDER_SM_GET_SERVICE, &stats.handle);
if (error != nullptr) {
FF_DEBUG("Looking up the \"%s\" service failed, so no time remaining is reported: %s",
FF_BATTERY_ANDROID_STATS_SERVICE, error);
} else {
fillTimeRemaining(&binder, stats.handle, timeRemainingTransaction, results);
}
// `BatteryStatus` has five values and only two of them say something the powered bits do not, so
// the trace spells the mapping out: anything that is not 2 or 3 leaves the bits clear, which is
// what UNKNOWN (1), NOT_CHARGING (4) and FULL (5) all come to.
// the trace spells the mapping out: anything that is not 2 or 3 leaves the charging / discharging
// bits clear, which is what UNKNOWN (1), NOT_CHARGING (4) and FULL (5) all come to -- that a
// charger is attached is stated by the bits the plug_type property fills in, not by this one. The
// temperature is not repeated here: it was traced where it was read, and it is the field of the
// three that can be missing without anything being wrong.
FF_DEBUG("Reporting one battery: capacity %.0f%%, status %" PRIu64 " -> bits 0x%x "
"(2 charging, 3 discharging; 1/4/5 leave them clear). The registrar answers no "
"temperature and no technology, so both stay unset",
battery->capacity, status, (unsigned) battery->status);
"(2 charging, 3 discharging; 1/4/5 leave them clear), %d s remaining (-1 when there is no "
"estimate). The technology stays unset: it rides the sticky broadcast, which this route "
"does not read",
battery->capacity, status, (unsigned) battery->status, battery->timeRemaining);
return nullptr;
}
// `dumpsys battery` prints what `BatteryService.dumpInternal()` prints: the whole of the health HAL's
// `HealthInfo`, plus the timestamps the service keeps for its own broadcast rate limiter. It is a flat
// `key: value` list read by name, which is what makes it the sturdier of the two routes -- a vendor
// that prints extra keys (some vendors do: `engine`, `soc decimal`, `adapter power`, `board temp status`,
// `low bat status`, `reverse wl chg status`, `reverse wl chg exception`, `chg shut vbat`,
// `last mode flag` and `last mode keep time`, in the middle of the list) only adds keys nobody asks
// for, and a release that appends a field costs nothing. The binder reply is positional and breaks
// silently instead.
//
// What each key carries, per `android.hardware.health.HealthInfo`:
//
// AC / USB / Wireless / Dock powered -- `charger*Online`, the `plugged` bits of the
// ACTION_BATTERY_CHANGED broadcast. Only the first three have a `FF_BATTERY_STATUS_*`
// counterpart; `Dock powered` (`BATTERY_PLUGGED_DOCK`) has none and is not read.
// Max charging current / Max charging voltage -- `maxChargingCurrentMicroamps` (µA) and
// `maxChargingVoltageMicrovolts` (µV): what the charger offers, not what the battery is drawing.
// Charge counter -- `batteryChargeCounterUah` (µAh), what is left in the pack. The unit is not
// honoured everywhere: MIUI reports the same number as its own dump and three orders of
// magnitude below what the registrar answers for the same battery.
// status -- `batteryStatus`, i.e. `BatteryStatus`: 1 UNKNOWN, 2 CHARGING, 3 DISCHARGING,
// 4 NOT_CHARGING (a charger is attached and the battery is deliberately not taking current),
// 5 FULL.
// health -- `batteryHealth`, i.e. `BatteryHealth`: 1 UNKNOWN, 2 GOOD, 3 OVERHEAT, 4 DEAD,
// 5 OVER_VOLTAGE, 6 UNSPECIFIED_FAILURE, 7 COLD, and, in newer releases, 8 FAIR, 11 NOT_AVAILABLE
// and 12 INCONSISTENT.
// present -- `batteryPresent`, whether the pack is in the device at all.
// level / scale -- `batteryLevel`, the remaining capacity in percent, and `BATTERY_SCALE`, the value
// 100% is expressed in. The scale has been 100 on every release, but it is printed rather than
// assumed, so it is read.
// voltage -- `batteryVoltageMillivolts`. The unit in the name is the recent one: the HAL 1.0 field
// was `batteryVoltage` in microvolts, and the AIDL notes that implementations had always filled it
// in millivolts, so the rename only wrote down the de-facto unit.
// temperature -- `batteryTemperatureTenthsCelsius`, tenths of a degree Celsius.
// technology -- `batteryTechnology`, e.g. "Li-poly". The registrar answers no such property, so
// together with the temperature this is what the route is here for.
// Charging state -- `chargingState`, i.e. `BatteryChargingState`: 0 INVALID, 1 NORMAL, 2 TOO_COLD,
// 3 TOO_HOT, 4 LONG_LIFE, 5 ADAPTIVE.
// Charging policy -- `chargingPolicy`, i.e. `BatteryChargingPolicy`: 0 INVALID, 1 DEFAULT,
// 2 LONG_LIFE, 3 ADAPTIVE. 0 in either means the HAL did not report it.
// Capacity level -- `batteryCapacityLevel`, i.e. `BatteryCapacityLevel`: -1 UNSUPPORTED,
// 0 UNKNOWN, 1 CRITICAL, 2 LOW, 3 NORMAL, 4 HIGH, 5 FULL.
//
// Three keys are not battery readings at all and are skipped: the two `Time when the latest updated
// value of the ... was sent via battery changed broadcast` lines and `The last voltage value sent via
// the battery changed broadcast` are what `BatteryService` keeps to rate limit the broadcast.
//
// `FFBatteryResult` has a place for the status enum, the level pair, the temperature and the
// technology, and for the three powered booleans and the capacity level in the form of status bits.
// `voltage`, `health`, `Charge counter` and the two maximums are read by nothing -- there is no field
// to put them in -- and are described above so that the next reader does not have to go back to the
// service to find out what they are.
static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) {
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
if (ffProcessAppendStdOut(&buf, (char*[]) {
"/system/bin/dumpsys",
"battery",
nullptr,
}) != nullptr ||
buf.length == 0) {
return "Executing `/system/bin/dumpsys battery` failed";
}
if (!ffStrbufStartsWithS(&buf, "Current Battery Service state:\n")) {
// A refusal, which ffDetectBattery has already ruled out by looking at the UID: an app UID gets
// `Can't find service: battery` on Android 16, and the permission denial some other
// release would print lands here too. Both exit 0, so this is the only place that notices.
return "Invalid `/system/bin/dumpsys battery` result";
}
const char* start = buf.chars + strlen("Current Battery Service state:\n");
FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();
if (!ffParsePropLines(start, "present: ", &temp) || !ffStrbufEqualS(&temp, "true")) {
return nullptr;
}
ffStrbufClear(&temp);
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
initResult(battery);
if (ffParsePropLines(start, "AC powered: ", &temp) && ffStrbufEqualS(&temp, "true")) {
battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
}
ffStrbufClear(&temp);
if (ffParsePropLines(start, "USB powered: ", &temp) && ffStrbufEqualS(&temp, "true")) {
battery->status |= FF_BATTERY_STATUS_USB_CONNECTED;
}
ffStrbufClear(&temp);
if (ffParsePropLines(start, "Wireless powered: ", &temp) && ffStrbufEqualS(&temp, "true")) {
battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED;
}
ffStrbufClear(&temp);
// NOT_CHARGING and FULL both mean a charger is attached, which the powered bits already say, and
// neither is charging or discharging in the sense `FF_BATTERY_STATUS` means. UNKNOWN is mapped
// because it is a different statement from the service not answering at all.
if (ffParsePropLines(start, "status: ", &temp)) {
switch (ffStrbufToUInt(&temp, 0)) {
case FF_BATTERY_ANDROID_STATUS_CHARGING:
battery->status |= FF_BATTERY_STATUS_CHARGING;
break;
case FF_BATTERY_ANDROID_STATUS_DISCHARGING:
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
break;
case FF_BATTERY_ANDROID_STATUS_UNKNOWN:
battery->status |= FF_BATTERY_STATUS_UNKNOWN;
break;
default:
break;
}
}
ffStrbufClear(&temp);
// CRITICAL is the one capacity level that says something the percentage does not. -1 (UNSUPPORTED)
// and 0 (UNKNOWN) are what a HAL that does not implement the property reports, and 2 to 5 only
// restate the level.
if (ffParsePropLines(start, "Capacity level: ", &temp) &&
ffStrbufToSInt(&temp, -1) == FF_BATTERY_ANDROID_CAPACITY_LEVEL_CRITICAL) {
battery->status |= FF_BATTERY_STATUS_CRITICAL;
}
ffStrbufClear(&temp);
{
double level = 0, scale = 0;
if (ffParsePropLines(start, "level: ", &temp)) {
level = ffStrbufToDouble(&temp, -DBL_MAX);
}
ffStrbufClear(&temp);
if (ffParsePropLines(start, "scale: ", &temp)) {
scale = ffStrbufToDouble(&temp, -DBL_MAX);
}
ffStrbufClear(&temp);
if (level > 0 && scale > 0) {
battery->capacity = level * 100 / scale;
}
}
if (options->temp) {
if (ffParsePropLines(start, "temperature: ", &temp)) {
battery->temperature = ffStrbufToDouble(&temp, FF_BATTERY_TEMP_UNSET);
if (battery->temperature != FF_BATTERY_TEMP_UNSET) {
battery->temperature /= 10.0; // Android returns temperature in tenths of a degree
}
}
ffStrbufClear(&temp);
}
ffParsePropLines(start, "technology: ", &battery->technology);
return nullptr;
}
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
// `dumpsys battery` needs android.permission.DUMP, which only the shell UID and root hold: an app
// UID is answered with `Can't find service: battery` on stdout and a zero exit status, so forking
// it there costs a child process and cannot succeed. It is also the richer of the two routes --
// the registrar answers neither temperature nor technology, and the dump is what spells out the
// status enum -- so it is tried first wherever it is allowed to run, and the binder route covers
// everything else at ~0.3 ms. termux-api was dropped: it returns nothing on this device and can
// hang for minutes, the same reason its camera path was removed.
if (ffAndroidIsRootOrShell(instance.state.platform.uid)) {
const char* error = parseDumpsys(options, results);
if (error == nullptr) {
return nullptr;
}
FF_DEBUG("`/system/bin/dumpsys battery` failed: %s. Using the binder route instead", error);
}
return parseBinder(results);
}
+2
View File
@@ -1,5 +1,6 @@
#include "fastfetch.h"
#include "battery.h"
#include "common/debug.h"
#include "common/apple/cf_helpers.h"
#include "common/apple/smc_temps.h"
@@ -9,6 +10,7 @@
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("AppleSmartBattery"), &iterator) != kIOReturnSuccess) {
FF_DEBUG("IOServiceGetMatchingServices() failed");
return "IOServiceGetMatchingServices() failed";
}
+5
View File
@@ -1,4 +1,5 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/sysctl.h"
#include "common/io.h"
#include "battery.h"
@@ -6,6 +7,8 @@
#include <dev/acpica/acpiio.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
@@ -14,6 +17,7 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
int units = ffSysctlGetInt("hw.acpi.battery.units", -100);
if (units < 0) {
FF_DEBUG("sysctlbyname(\"hw.acpi.battery.units\") failed: %s", strerror(errno));
return "sysctlbyname(\"hw.acpi.battery.units\") failed";
}
@@ -23,6 +27,7 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
FF_AUTO_CLOSE_FD int acpifd = open("/dev/acpi", O_RDONLY | O_CLOEXEC);
if (acpifd < 0) {
FF_DEBUG("open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC) failed: %s", strerror(errno));
return "open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC) failed";
}
+6
View File
@@ -1,23 +1,28 @@
#include "fastfetch.h"
#include "battery.h"
#include "common/debug.h"
#include "common/io.h"
#include <private/device/power_managment.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
const char* parseBattery(int dfd, const char* battId, FFlist* results) {
FF_AUTO_CLOSE_FD int fd = openat(dfd, battId, O_RDWR);
if (fd < 0) {
FF_DEBUG("openat(%s) failed: %s", battId, strerror(errno));
return "openat() failed";
}
acpi_battery_info basic = {};
if (ioctl(fd, GET_BATTERY_INFO, &basic, sizeof(basic)) != 0) {
FF_DEBUG("ioctl(GET_BATTERY_INFO) failed");
return "ioctl(GET_BATTERY_INFO) failed";
}
acpi_extended_battery_info extended = {};
if (ioctl(fd, GET_EXTENDED_BATTERY_INFO, &extended, sizeof(extended)) != 0) {
FF_DEBUG("ioctl(GET_EXTENDED_BATTERY_INFO) failed");
return "ioctl(GET_EXTENDED_BATTERY_INFO) failed";
}
@@ -56,6 +61,7 @@ const char* parseBattery(int dfd, const char* battId, FFlist* results) {
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/dev/power/acpi_battery/");
if (!dir) {
FF_DEBUG("opendir(\"/dev/power/acpi_battery/\") failed: %s", strerror(errno));
return "opendir(/dev/power/acpi_battery) failed";
}
+2
View File
@@ -6,6 +6,7 @@
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power
@@ -176,6 +177,7 @@ static bool parseBattery(int dfd, const char* id, FFBatteryOptions* options, FFl
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/power_supply/");
if (dirp == nullptr) {
FF_DEBUG("opendir(\"/sys/class/power_supply/\") failed: %s", strerror(errno));
return "opendir(\"/sys/class/power_supply/\") == nullptr";
}
+3
View File
@@ -1,4 +1,5 @@
#include "battery.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/FFstrbuf.h"
#include "common/strutil.h"
@@ -17,11 +18,13 @@
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(_PATH_SYSMON) failed: %s", strerror(errno));
return "open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed";
}
prop_dictionary_t root = nullptr;
if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0) {
FF_DEBUG("prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed");
return "prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed";
}
+4
View File
@@ -1,6 +1,8 @@
#include "battery.h"
#include "common/debug.h"
#include "common/io.h"
#include <string.h>
#include <machine/apmvar.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -10,12 +12,14 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
FF_AUTO_CLOSE_FD int devfd = open("/dev/apm", O_RDONLY | O_CLOEXEC);
if (devfd < 0) {
FF_DEBUG("open(\"/dev/apm\") failed: %s", strerror(errno));
return "open(dev/apm, O_RDONLY | O_CLOEXEC) failed";
}
struct apm_power_info info = {};
if (ioctl(devfd, APM_IOC_GETPOWER, &info) < 0) {
FF_DEBUG("ioctl(APM_IOC_GETPOWER) failed: %s", strerror(errno));
return "ioctl(APM_IOC_GETPOWER) failed";
}
+3
View File
@@ -1,4 +1,5 @@
#include "battery.h"
#include "common/debug.h"
#include <kstat.h>
@@ -50,6 +51,7 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
// https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/powertop/common/battery.c
[[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
if (!kc) {
FF_DEBUG("kstat_open() failed");
return "kstat_open() failed";
}
@@ -75,6 +77,7 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
if (!getUint32(bif, "bif_last_cap", &lastCapacity) ||
!getUint32(bst, "bst_rem_cap", &remainingCapacity) ||
!getUint32(bst, "bst_state", &state)) {
FF_DEBUG("kstat_data_lookup() failed for bif_last_cap, bst_rem_cap or bst_state");
return "kstat_data_lookup() failed";
}
+4 -3
View File
@@ -1,10 +1,10 @@
#define INITGUID
#include "battery.h"
#include "common/endian.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/mallocHelper.h"
#include "common/windows/nt.h"
#include "common/windows/unicode.h"
#include "common/windows/wmi.h"
@@ -51,7 +51,7 @@ static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULON
return entry;
}
static const char* queryWmiAllData(const GUID* guid, const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) {
static const char* queryWmiAllData(const GUID* guid, [[maybe_unused]] const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) {
[[gnu::cleanup(ffCloseWmiBlock)]] HANDLE hBlock = nullptr;
ULONG status = WmiOpenBlock(guid, WMIGUID_QUERY, &hBlock);
if (status != ERROR_SUCCESS) {
@@ -66,6 +66,7 @@ static const char* queryWmiAllData(const GUID* guid, const char* guidStr, PWNODE
}
if (*pBufferSize == 0) {
FF_DEBUG("WMI: WmiQueryAllDataW(nullptr) returned no data");
return "WmiQueryAllDataW(nullptr) returned no data";
}
+4
View File
@@ -1,5 +1,6 @@
#include "bios.h"
#include "common/apple/cf_helpers.h"
#include "common/debug.h"
#include <IOKit/IOKitLib.h>
@@ -10,11 +11,13 @@ const char* ffDetectBios(FFBiosResult* bios) {
// For Intel
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceRom = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/rom");
if (!deviceRom) {
FF_DEBUG("IODeviceTree:/rom not found");
return "IODeviceTree:/rom not found";
}
FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef deviceRomProps = nullptr;
if (IORegistryEntryCreateCFProperties(deviceRom, &deviceRomProps, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
FF_DEBUG("IORegistryEntryCreateCFProperties(deviceRom) failed");
return "IORegistryEntryCreateCFProperties(deviceRom) failed";
}
@@ -28,6 +31,7 @@ const char* ffDetectBios(FFBiosResult* bios) {
// For arm64
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t device = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/");
if (!device) {
FF_DEBUG("IODeviceTree:/ not found");
return "IODeviceTree:/ not found";
}
+3
View File
@@ -1,4 +1,5 @@
#include "bios.h"
#include "common/debug.h"
#include "common/smbios.h"
#ifdef _WIN32
@@ -46,11 +47,13 @@ static_assert(offsetof(FFSmbiosBios, ExtendedBiosRomSize) == 0x18,
const char* ffDetectBios(FFBiosResult* bios) {
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
if (!smbiosTable) {
FF_DEBUG("BIOS: failed to get SMBIOS data");
return "Failed to get SMBIOS data";
}
const FFSmbiosBios* data = (const FFSmbiosBios*) (*smbiosTable)[FF_SMBIOS_TYPE_BIOS];
if (!data) {
FF_DEBUG("BIOS: section is not found in SMBIOS data");
return "BIOS section is not found in SMBIOS data";
}
+2
View File
@@ -1,4 +1,5 @@
#include "bluetooth.h"
#include "common/debug.h"
#define L2CAP_SOCKET_CHECKED
#include <bluetooth.h>
@@ -31,6 +32,7 @@ const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FF
// struct hostent* ent = bt_gethostent();
if (bt_devenum((void*) enumDev, devices) < 0) {
FF_DEBUG("bt_devenum() failed");
return "bt_devenum() failed";
}
@@ -1,4 +1,5 @@
#include "bluetooth.h"
#include "common/debug.h"
#include "common/percent.h"
#include "common/strutil.h"
@@ -304,11 +305,13 @@ static const char* detectBluetooth(FFBluetoothOptions* options, FFlist* devices,
DBusMessage* managedObjects = ffDBusGetMethodReply(&dbus, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", nullptr, nullptr);
if (!managedObjects) {
FF_DEBUG("Failed to call GetManagedObjects");
return "Failed to call GetManagedObjects";
}
DBusMessageIter rootIter;
if (!dbus.lib->ffdbus_message_iter_init(managedObjects, &rootIter)) {
FF_DEBUG("Failed to get root iterator of GetManagedObjects");
dbus.lib->ffdbus_message_unref(managedObjects);
return "Failed to get root iterator of GetManagedObjects";
}
@@ -351,6 +354,7 @@ const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FF
return detectBluetooth(options, devices, connectedCount);
#else
FF_UNUSED(options, devices);
FF_DEBUG("Fastfetch was compiled without DBus support");
return "Fastfetch was compiled without DBus support";
#endif
}
@@ -1,4 +1,5 @@
#include "bluetooth.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include "common/windows/unicode.h"
@@ -64,6 +65,7 @@ static const char* detectBattery(FFlist* devices) {
// not in the media class at all, so narrowing the filter would lose it.
CONFIGRET status = CM_Get_Device_ID_List_SizeW(&idListLength, GUID_DEVCLASS_MEDIA_STRING, CM_GETIDLIST_FILTER_PRESENT);
if (status != CR_SUCCESS) {
FF_DEBUG("CM_Get_Device_ID_List_SizeW failed: %s", ffDebugConfigRet(status));
return "CM_Get_Device_ID_List_SizeW failed";
}
@@ -73,11 +75,13 @@ static const char* detectBattery(FFlist* devices) {
FF_AUTO_FREE wchar_t* idList = (wchar_t*) malloc((size_t) idListLength * sizeof(wchar_t));
if (!idList) {
FF_DEBUG("malloc() failed");
return "malloc() failed";
}
status = CM_Get_Device_ID_ListW(GUID_DEVCLASS_MEDIA_STRING, idList, idListLength, CM_GETIDLIST_FILTER_PRESENT);
if (status != CR_SUCCESS) {
FF_DEBUG("CM_Get_Device_ID_ListW failed: %s", ffDebugConfigRet(status));
return "CM_Get_Device_ID_ListW failed";
}
@@ -137,6 +141,7 @@ static const char* detectClassic(FFBluetoothOptions* options, FFlist* devices /*
&btdi);
if (!hFind) {
if (GetLastError() != ERROR_NO_MORE_ITEMS) {
FF_DEBUG("BluetoothFindFirstDevice() failed: %s", ffDebugWin32Error(GetLastError()));
return "BluetoothFindFirstDevice() failed";
}
// "Nothing found" is not an error here: this search only ever sees BR/EDR devices, and a
+14 -4
View File
@@ -1,5 +1,6 @@
extern "C" {
#include "bluetooth.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/percent.h"
#include "common/strutil.h"
@@ -280,24 +281,32 @@ extern "C" const char* ffBluetoothDetectLe(FFBluetoothOptions* options, FFlist*
}
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics2>* ledeviceStatics = nullptr;
if (FAILED(ffGetActivationFactory<winrt::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics2>(L"Windows.Devices.Bluetooth.BluetoothLEDevice", &ledeviceStatics)) || !ledeviceStatics) {
HRESULT ledeviceHr = ffGetActivationFactory<winrt::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics2>(L"Windows.Devices.Bluetooth.BluetoothLEDevice", &ledeviceStatics);
if (FAILED(ledeviceHr) || !ledeviceStatics) {
FF_DEBUG("winrt: RoGetActivationFactory(BluetoothLEDevice) failed: %s", ffDebugHResult(ledeviceHr));
return "winrt: RoGetActivationFactory(BluetoothLEDevice) failed";
}
[[gnu::cleanup(ffDeleteHstring)]] HSTRING selector = nullptr;
if (FAILED(ledeviceStatics->GetDeviceSelectorFromPairingState(true, reinterpret_cast<void**>(&selector))) || !selector) {
HRESULT selectorHr = ledeviceStatics->GetDeviceSelectorFromPairingState(true, reinterpret_cast<void**>(&selector));
if (FAILED(selectorHr) || !selector) {
FF_DEBUG("winrt: GetDeviceSelectorFromPairingState() failed: %s", ffDebugHResult(selectorHr));
return "winrt: GetDeviceSelectorFromPairingState() failed";
}
// Without additional properties the map carries only the eight generic ones, and every AEP key
// is absent, so the list is not optional.
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Foundation::Collections::IIterable<winrt::hstring>>* properties = nullptr;
if (FAILED(ffWinrtCreateHstringIterable(ffBluetoothLeProperties, (uint32_t) ARRAY_SIZE(ffBluetoothLeProperties), &properties)) || !properties) {
HRESULT propertiesHr = ffWinrtCreateHstringIterable(ffBluetoothLeProperties, (uint32_t) ARRAY_SIZE(ffBluetoothLeProperties), &properties);
if (FAILED(propertiesHr) || !properties) {
FF_DEBUG("winrt: building the additional-properties list failed: %s", ffDebugHResult(propertiesHr));
return "winrt: building the additional-properties list failed";
}
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Devices::Enumeration::IDeviceInformationStatics>* enumerationStatics = nullptr;
if (FAILED(ffGetActivationFactory<winrt::Windows::Devices::Enumeration::IDeviceInformationStatics>(L"Windows.Devices.Enumeration.DeviceInformation", &enumerationStatics)) || !enumerationStatics) {
HRESULT deviceInfoHr = ffGetActivationFactory<winrt::Windows::Devices::Enumeration::IDeviceInformationStatics>(L"Windows.Devices.Enumeration.DeviceInformation", &enumerationStatics);
if (FAILED(deviceInfoHr) || !enumerationStatics) {
FF_DEBUG("winrt: RoGetActivationFactory(DeviceInformation) failed: %s", ffDebugHResult(deviceInfoHr));
return "winrt: RoGetActivationFactory(DeviceInformation) failed";
}
@@ -307,6 +316,7 @@ extern "C" const char* ffBluetoothDetectLe(FFBluetoothOptions* options, FFlist*
},
&found);
if (FAILED(hr) || !found) {
FF_DEBUG("winrt: DeviceInformation::FindAllAsync() failed: %s", ffDebugHResult(hr));
return "winrt: DeviceInformation::FindAllAsync() failed";
}
@@ -1,4 +1,5 @@
#include "bluetoothradio.h"
#include "common/debug.h"
#include "common/strutil.h"
#ifdef FF_HAVE_DBUS
@@ -36,6 +37,7 @@ array [
static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter) {
if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) {
FF_DEBUG("Expected dict entry");
return "Expected dict entry";
}
@@ -43,6 +45,7 @@ static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBu
dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);
if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) {
FF_DEBUG("Expected dict entry key to be a string");
return "Expected dict entry key to be a string";
}
@@ -82,17 +85,20 @@ static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const cha
DBusMessage* properties = ffDBusGetMethodReply(dbus, "org.bluez", objPath, "org.freedesktop.DBus.Properties", "GetAll", "org.bluez.Adapter1", nullptr);
if (!properties) {
FF_DEBUG("Failed to call org.freedesktop.DBus.Properties.GetAll");
return "Failed to call org.freedesktop.DBus.Properties.GetAll";
}
DBusMessageIter rootIter;
if (!dbus->lib->ffdbus_message_iter_init(properties, &rootIter)) {
dbus->lib->ffdbus_message_unref(properties);
FF_DEBUG("Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll");
return "Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll";
}
if (dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY) {
dbus->lib->ffdbus_message_unref(properties);
FF_DEBUG("Expected array");
return "Expected array";
}
@@ -110,6 +116,7 @@ static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const cha
static const char* detectBluetooth(FFlist* devices) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth");
if (dirp == nullptr) {
FF_DEBUG("opendir(\"/sys/class/bluetooth\") failed: %s", strerror(errno));
return "Failed to open /sys/class/bluetooth";
}
@@ -148,6 +155,7 @@ const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
return detectBluetooth(devices);
#else
FF_UNUSED(devices)
FF_DEBUG("Fastfetch was compiled without DBus support");
return "Fastfetch was compiled without DBus support";
#endif
}
@@ -1,4 +1,5 @@
#include "bluetoothradio.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/io.h"
#include "common/windows/unicode.h"
@@ -64,16 +65,20 @@ const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
&hRadio);
if (!hFind) {
if (GetLastError() == ERROR_NO_MORE_ITEMS) {
FF_DEBUG("No Bluetooth radios found or service disabled");
return "No Bluetooth radios found or service disabled";
} else {
FF_DEBUG("BluetoothFindFirstRadio() failed: %s", ffDebugWin32Error(GetLastError()));
return "BluetoothFindFirstRadio() failed";
}
}
do {
FF_AUTO_CLOSE_FD HANDLE hCurrent = hRadio;
BTH_LOCAL_RADIO_INFO blri;
DWORD returned;
if (!DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, nullptr, 0, &blri, sizeof(blri), &returned, nullptr)) {
if (!DeviceIoControl(hCurrent, IOCTL_BTH_GET_LOCAL_INFO, nullptr, 0, &blri, sizeof(blri), &returned, nullptr)) {
continue;
}
@@ -87,10 +92,8 @@ const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
device->lmpSubversion = blri.radioInfo.lmpSubversion;
ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg));
device->enabled = true;
device->connectable = ffBluetoothIsConnectable(hRadio);
device->discoverable = ffBluetoothIsDiscoverable(hRadio);
NtClose(hRadio);
device->connectable = ffBluetoothIsConnectable(hCurrent);
device->discoverable = ffBluetoothIsDiscoverable(hCurrent);
} while (ffBluetoothFindNextRadio(hFind, &hRadio));
ffBluetoothFindRadioClose(hFind);
+2
View File
@@ -1,10 +1,12 @@
#include "board.h"
#include "common/debug.h"
#include "common/apple/cf_helpers.h"
const char* ffDetectBoard(FFBoardResult* result) {
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t service = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("IOPlatformExpertDevice"));
if (!service) {
FF_DEBUG("No IOPlatformExpertDevice found");
return "No IOPlatformExpertDevice found";
}
+3
View File
@@ -1,4 +1,5 @@
#include "board.h"
#include "common/debug.h"
#include "common/smbios.h"
typedef struct [[gnu::packed]] FFSmbiosBaseboard {
@@ -23,11 +24,13 @@ static_assert(offsetof(FFSmbiosBaseboard, ContainedObjectHandles) == 0x0F,
const char* ffDetectBoard(FFBoardResult* board) {
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
if (!smbiosTable) {
FF_DEBUG("Baseboard information: failed to get SMBIOS data");
return "Failed to get SMBIOS data";
}
const FFSmbiosBaseboard* data = (const FFSmbiosBaseboard*) (*smbiosTable)[FF_SMBIOS_TYPE_BASEBOARD_INFO];
if (!data) {
FF_DEBUG("Baseboard information: section is not found in SMBIOS data");
return "Baseboard information section is not found in SMBIOS data";
}
+7
View File
@@ -1,5 +1,6 @@
#include "bootmgr.h"
#include "common/io.h"
#include "common/debug.h"
#include "common/apple/cf_helpers.h"
#include <IOKit/IOKitLib.h>
@@ -8,15 +9,18 @@ static const char* detectSecureBoot(bool* result) {
#if __aarch64__
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen");
if (!entryDevice) {
FF_DEBUG("IORegistryEntryFromPath(\"IODeviceTree:/chosen\") failed");
return "IORegistryEntryFromPath() failed";
}
FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("secure-boot"), kCFAllocatorDefault, kNilOptions);
if (!prop) {
FF_DEBUG("IORegistryEntryCreateCFProperty(\"secure-boot\") failed");
return "IORegistryEntryCreateCFProperty() failed";
}
if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0) {
FF_DEBUG("Invalid secure-boot property");
return "Invalid secure-boot property";
}
@@ -24,15 +28,18 @@ static const char* detectSecureBoot(bool* result) {
#else
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/options");
if (!entryDevice) {
FF_DEBUG("IORegistryEntryFromPath(\"IODeviceTree:/options\") failed");
return "IORegistryEntryFromPath() failed";
}
FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("94b73556-2197-4702-82a8-3e1337dafbfb:AppleSecureBootPolicy"), kCFAllocatorDefault, 0);
if (!prop) {
FF_DEBUG("IORegistryEntryCreateCFProperty(AppleSecureBootPolicy) failed");
return "IORegistryEntryCreateCFProperty() failed";
}
if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0) {
FF_DEBUG("Invalid secure-boot property");
return "Invalid secure-boot property";
}
+4
View File
@@ -1,5 +1,6 @@
#include "bootmgr.h"
#include "efi_helper.h"
#include "common/debug.h"
#include "common/io.h"
#ifdef __OpenBSD__
@@ -22,6 +23,7 @@ typedef uint16_t efi_char;
const char* ffDetectBootmgr(FFBootmgrResult* result) {
FF_AUTO_CLOSE_FD int efifd = open("/dev/efi", O_RDWR | O_CLOEXEC);
if (efifd < 0) {
FF_DEBUG("open(\"/dev/efi\") failed: %s", strerror(errno));
return "open(/dev/efi) failed";
}
@@ -35,6 +37,7 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
ioc.name = (efi_char[]) { 'B', 'o', 'o', 't', 'C', 'u', 'r', 'r', 'e', 'n', 't', '\0' };
ioc.namesize = sizeof("BootCurrent") * 2;
if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize != 2) {
FF_DEBUG("ioctl(EFIIOC_VAR_GET, BootCurrent) failed");
return "ioctl(EFIIOC_VAR_GET, BootCurrent) failed";
}
@@ -46,6 +49,7 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
ioc.name = (efi_char[]) { 'B', 'o', 'o', 't', hex[0], hex[1], hex[2], hex[3], '\0' };
ioc.namesize = sizeof("Boot####") * 2;
if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize == sizeof(buffer)) {
FF_DEBUG("ioctl(EFIIOC_VAR_GET, Boot####) failed");
return "ioctl(EFIIOC_VAR_GET, Boot####) failed";
}
+4
View File
@@ -1,15 +1,18 @@
#include "bootmgr.h"
#include "common/debug.h"
#include "common/io.h"
#include "efi_helper.h"
#include <stdalign.h>
#define FF_EFIVARS_PATH_PREFIX "/sys/firmware/efi/efivars/"
#define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c"
const char* ffDetectBootmgr(FFBootmgrResult* result) {
alignas(uint16_t) uint8_t buffer[2048];
if (ffReadFileData(FF_EFIVARS_PATH_PREFIX "BootCurrent-" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) != 6) {
FF_DEBUG("Failed to read efivar: BootCurrent");
return "Failed to read efivar: BootCurrent";
}
@@ -19,6 +22,7 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
ssize_t size = ffReadFileData((const char*) buffer, sizeof(buffer), buffer);
if (size < 5 + (int) sizeof(FFEfiLoadOption) || size == (ssize_t) sizeof(buffer)) {
FF_DEBUG("Failed to read efivar: Boot####");
return "Failed to read efivar: Boot####";
}
+22 -8
View File
@@ -1,4 +1,5 @@
#include "bootmgr.h"
#include "common/debug.h"
#include "efi_helper.h"
#include "common/io.h"
#include "common/windows/nt.h"
@@ -8,7 +9,9 @@
const char* enablePrivilege(const wchar_t* privilege) {
FF_AUTO_CLOSE_FD HANDLE token = nullptr;
if (!NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))) {
NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtOpenProcessToken() failed: %s", ffDebugNtStatus(status));
return "NtOpenProcessToken() failed";
}
@@ -18,15 +21,18 @@ const char* enablePrivilege(const wchar_t* privilege) {
(LUID_AND_ATTRIBUTES) { .Attributes = SE_PRIVILEGE_ENABLED } },
};
if (!LookupPrivilegeValueW(nullptr, privilege, &tp.Privileges[0].Luid)) {
FF_DEBUG("LookupPrivilegeValue() failed: %s", ffDebugWin32Error(GetLastError()));
return "LookupPrivilegeValue() failed";
}
NTSTATUS status = NtAdjustPrivilegesToken(token, false, &tp, sizeof(tp), nullptr, nullptr);
status = NtAdjustPrivilegesToken(token, false, &tp, sizeof(tp), nullptr, nullptr);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtAdjustPrivilegesToken() failed: %s", ffDebugNtStatus(status));
return "NtAdjustPrivilegesToken() failed";
}
if (status == STATUS_NOT_ALL_ASSIGNED) {
FF_DEBUG("NtAdjustPrivilegesToken() did not assign the privilege: %s", ffDebugNtStatus(status));
return "The token does not have the specified privilege; try sudo please";
}
@@ -39,16 +45,21 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
return err;
}
GUID efiGlobalGuid;
if (!NT_SUCCESS(RtlGUIDFromString(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"{" FF_EFI_GLOBAL_GUID L"}"), &efiGlobalGuid))) {
return "RtlGUIDFromString() failed";
}
GUID efiGlobalGuid = {
.Data1 = 0x8be4df61,
.Data2 = 0x93ca,
.Data3 = 0x11d2,
.Data4 = { 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c },
};
ULONG size = sizeof(result->order);
if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, nullptr))) {
NTSTATUS status = NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, nullptr);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(BootCurrent) failed: %s", ffDebugNtStatus(status));
return "NtQuerySystemEnvironmentValueEx(BootCurrent) failed";
}
if (size != sizeof(result->order)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(BootCurrent) returned unexpected size");
return "NtQuerySystemEnvironmentValueEx(BootCurrent) returned unexpected size";
}
@@ -56,10 +67,13 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
wchar_t key[9];
swprintf(key, ARRAY_SIZE(key), L"Boot%04X", result->order);
size = sizeof(buffer);
if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(key), &efiGlobalGuid, buffer, &size, nullptr))) {
status = NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(key), &efiGlobalGuid, buffer, &size, nullptr);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(Boot####) failed: %s", ffDebugNtStatus(status));
return "NtQuerySystemEnvironmentValueEx(Boot####) failed";
}
if (size < sizeof(FFEfiLoadOption) || size == ARRAY_SIZE(buffer)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(Boot####) returned unexpected size");
return "NtQuerySystemEnvironmentValueEx(Boot####) returned unexpected size";
}
-2
View File
@@ -18,5 +18,3 @@ typedef struct FFEfiLoadOption {
} FFEfiLoadOption;
bool ffEfiFillLoadOption(const FFEfiLoadOption* efiOption, FFBootmgrResult* result);
#define FF_EFI_GLOBAL_GUID "8be4df61-93ca-11d2-aa0d-00e098032b8c"
+48
View File
@@ -11,6 +11,7 @@ typedef struct FFBrightnessResult {
enum {
FF_DDC_EDID_ADDR = 0x50u,
FF_DDC_EDID_WRITE_ADDR = FF_DDC_EDID_ADDR << 1,
FF_DDC_CI_ADDR = 0x37u,
FF_DDC_CI_WRITE_ADDR = FF_DDC_CI_ADDR << 1,
FF_DDC_CI_READ_ADDR = FF_DDC_CI_WRITE_ADDR | 1,
@@ -19,8 +20,55 @@ enum {
FF_DDC_CI_SET_VCP = 0x03u,
FF_DDC_CI_COMMAND_PACKET = 0x80u,
FF_DDC_CI_LUMINANCE_OPCODE = 0x10u,
FF_DDC_CI_VCP_RESPONSE_LENGTH = 8u,
FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE = 11u,
FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE = 12u,
FF_DDC_CI_RESPONSE_CHECKSUM_ADDR = 0x50u,
};
#define FF_BRIGHTNESS_DDCCI_SLEEP_SKIP ((uint32_t) -1)
#define FF_DDC_CI_MAKE_HEADER(len) (FF_DDC_CI_COMMAND_PACKET | ((len) & 0x7F))
static inline uint8_t ffDdcCiChecksum(uint8_t address, const uint8_t* data, size_t length) {
uint8_t checksum = address;
for (size_t i = 0; i < length; ++i) {
checksum ^= data[i];
}
return checksum;
}
static inline void ffDdcCiBuildGetVcpRequest(uint8_t request[5]) {
request[0] = FF_DDC_CI_VCP_COMMAND;
request[1] = FF_DDC_CI_MAKE_HEADER(2);
request[2] = FF_DDC_CI_GET_VCP;
request[3] = FF_DDC_CI_LUMINANCE_OPCODE;
request[4] = ffDdcCiChecksum(FF_DDC_CI_WRITE_ADDR, request, 4);
}
static inline bool ffDdcCiValidateGetVcpResponse(const uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
if (response[0] != FF_DDC_CI_WRITE_ADDR
|| response[1] != FF_DDC_CI_MAKE_HEADER(FF_DDC_CI_VCP_RESPONSE_LENGTH)
|| response[2] != 0x02u
|| response[3] != 0x00u
|| response[4] != FF_DDC_CI_LUMINANCE_OPCODE
|| response[5] != 0x00u) {
return false;
}
return ffDdcCiChecksum(FF_DDC_CI_RESPONSE_CHECKSUM_ADDR, response, FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE - 1u) == response[FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE - 1u];
}
#define FF_I2C_DEVICE_CACHE_MAX 32
typedef struct FFBrightnessDdcCache {
int devices[FF_I2C_DEVICE_CACHE_MAX];
size_t count;
} FFBrightnessDdcCache;
static inline FFstrbuf ffBrightnessDdcCachePath(void) {
FFstrbuf path = ffStrbufCreateCopy(&instance.state.platform.cacheDir);
ffStrbufEnsureEndsWithC(&path, '/');
ffStrbufAppendS(&path, "fastfetch/brightness/i2c_cache.txt");
return path;
}
const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult
@@ -1,6 +1,7 @@
#include "brightness.h"
#include "detection/displayserver/displayserver.h"
#include "common/apple/cf_helpers.h"
#include "common/debug.h"
#include "common/edidHelper.h"
#include "common/time.h"
@@ -28,6 +29,7 @@ typedef CFTypeRef IOAVServiceRef;
// Works for internal display
static const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result) {
if (DisplayServicesGetBrightness == nullptr) {
FF_DEBUG("DisplayServices function DisplayServicesGetBrightness is not available");
return "DisplayServices function DisplayServicesGetBrightness is not available";
}
@@ -53,11 +55,13 @@ static const char* detectWithDisplayServices(const FFDisplayServerResult* displa
// Works for Apple Silicon and USB-C adapter connection ( but not HTMI )
static const char* detectWithDdcci([[maybe_unused]] const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C) {
FF_DEBUG("IOAVService is not available");
return "IOAVService is not available";
}
FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("DCPAVServiceProxy"), &iterator) != kIOReturnSuccess) {
FF_DEBUG("IOServiceGetMatchingServices() failed");
return "IOServiceGetMatchingServices() failed";
}
@@ -154,10 +158,12 @@ static IOOptionBits getSupportedTransactionType(void) {
static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
if (!CGSServiceForDisplayNumber) {
FF_DEBUG("CGSServiceForDisplayNumber is not available");
return "CGSServiceForDisplayNumber is not available";
}
IOOptionBits transactionType = getSupportedTransactionType();
if (transactionType == kIOI2CNoTransactionType) {
FF_DEBUG("No supported IOI2C transaction type found");
return "No supported IOI2C transaction type found";
}
+253 -45
View File
@@ -4,7 +4,11 @@
#include "common/kmod.h"
#include "common/debug.h"
#include "common/time.h"
#include "common/edidHelper.h"
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <unistd.h>
@@ -12,65 +16,261 @@
#if __has_include(<dev/iicbus/iic.h>)
#include <dev/iicbus/iic.h>
const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
// FIXME: doesn't work for me
for (char i = '0'; i <= '9'; ++i) {
char path[] = "/dev/iic0";
path[ARRAY_SIZE(path) - 2] = i;
// Try to load cache from file
static bool loadCacheFromFile(FFBrightnessDdcCache* cache) {
cache->count = 0;
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
bool valid = false;
FF_DEBUG("Brightness DDC/CI: loading I2C cache from %s", cacheDir.chars);
if (ffReadFileBuffer(cacheDir.chars, &cacheContent)) {
// Parse cache: first number is count, rest are device numbers
char* ptr = cacheContent.chars;
char* end = ptr + cacheContent.length;
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
int err = errno;
if (err == ENOENT) {
FF_DEBUG("No more I2C devices");
break;
} else if (i == '0' && !ffKmodLoaded("iicbus")) {
FF_DEBUG("iicbus module is not loaded");
return "No I2C devices found and iicbus module is not loaded";
// Read count
char* next;
long count = strtol(ptr, &next, 10);
if (next != ptr && count > 0 && count <= FF_I2C_DEVICE_CACHE_MAX) {
ptr = next;
// Read device numbers and reject truncated or trailing data.
size_t parsedCount = 0;
for (; parsedCount < (size_t) count && ptr < end; ++parsedCount) {
long dev = strtol(ptr, &next, 10);
if (next == ptr || dev < 0 || dev >= 100) {
break;
}
cache->devices[parsedCount] = (int) dev;
ptr = next;
}
while (ptr < end && isspace((unsigned char) *ptr)) {
++ptr;
}
if (parsedCount == (size_t) count && ptr == end) {
cache->count = parsedCount;
valid = true;
for (size_t i = 0; i < cache->count; ++i) {
char path[32];
snprintf(path, sizeof(path), "/dev/iic%d", cache->devices[i]);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: cached bus %d is unavailable (errno=%d: %s)", cache->devices[i], error, strerror(error));
valid = false;
break;
}
}
}
}
FF_DEBUG("open(/dev/iic%c) failed: %s", i, strerror(errno));
continue;
}
uint8_t i2cIn[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 };
i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3];
if (!valid) {
FF_DEBUG("Brightness DDC/CI: I2C cache is missing or invalid");
cache->count = 0;
return false;
}
int ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_WRITE_ADDR, .flags = IIC_M_WR, .len = ARRAY_SIZE(i2cIn), .buf = i2cIn }, .nmsgs = 1 });
if (ret < 0) {
FF_DEBUG("First ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno));
continue;
FF_DEBUG("Brightness DDC/CI: loaded %zu bus(es) from cache", cache->count);
return true;
}
// Save cache to file
static void saveCacheToFile(const FFBrightnessDdcCache* cache) {
if (cache->count == 0) {
return;
}
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateF("%zu", cache->count);
for (size_t i = 0; i < cache->count; i++) {
ffStrbufAppendF(&cacheContent, " %d", cache->devices[i]);
}
if (ffWriteFileBuffer(cacheDir.chars, &cacheContent)) {
FF_DEBUG("Brightness DDC/CI: saved %zu bus(es) to %s", cache->count, cacheDir.chars);
} else {
FF_DEBUG("Brightness DDC/CI: failed to save cache to %s", cacheDir.chars);
}
}
static bool readDdcCiResponse(int fd, FFBrightnessOptions* options, uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
uint8_t request[5];
ffDdcCiBuildGetVcpRequest(request);
struct iic_msg msg = {
.slave = FF_DDC_CI_WRITE_ADDR,
.flags = IIC_M_WR,
.len = sizeof(request),
.buf = request
};
struct iic_rdwr_data data = {
.msgs = &msg,
.nmsgs = 1
};
int ioctlResult = ioctl(fd, I2CRDWR, &data);
if (ioctlResult < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: write request failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
return false;
}
ffTimeSleep(options->ddcciSleep);
uint8_t i2cOut[12] = {};
ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_READ_ADDR, // LSB will be overridden by kernel to set read bit
.flags = IIC_M_RD,
.len = ARRAY_SIZE(i2cOut),
.buf = i2cOut },
.nmsgs = 1 });
if (ret < 0) {
FF_DEBUG("Second ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno));
continue;
}
if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) {
FF_DEBUG("i2c out validation failed: i2cOut[2] = 0x%02x, i2cOut[3] = 0x%02x", i2cOut[2], i2cOut[3]);
continue;
msg.slave = FF_DDC_CI_WRITE_ADDR;
msg.flags = IIC_M_RD;
msg.len = FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE;
msg.buf = response;
ioctlResult = ioctl(fd, I2CRDWR, &data);
if (ioctlResult < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: read response failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
return false;
}
uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
if (!ffDdcCiValidateGetVcpResponse(response)) {
FF_DEBUG("Brightness DDC/CI: invalid response (addr=0x%02x, header=0x%02x, type=0x%02x, opcode=0x%02x, checksum=0x%02x)",
(unsigned) response[0], (unsigned) response[1], (unsigned) response[2], (unsigned) response[4], (unsigned) response[10]);
return false;
}
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
ffStrbufInitS(&brightness->name, path + strlen("/dev/"));
brightness->builtin = false;
FF_DEBUG("Brightness DDC/CI: valid response (fd=%d, current=%u, max=%u)", fd,
((unsigned) response[8] << 8u) | (unsigned) response[9],
((unsigned) response[6] << 8u) | (unsigned) response[7]);
return true;
}
return nullptr;
}
static bool scanI2cDevices(FFBrightnessOptions* options, FFBrightnessDdcCache* cache) {
FF_DEBUG("Brightness DDC/CI: scanning FreeBSD I2C buses");
cache->count = 0;
for (int i = 0; i < 100 && cache->count < FF_I2C_DEVICE_CACHE_MAX; i++) {
char path[32];
snprintf(path, sizeof(path), "/dev/iic%d", i);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
continue;
}
FF_DEBUG("Brightness DDC/CI: probing %s", path);
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
cache->devices[cache->count++] = i;
FF_DEBUG("Brightness DDC/CI: found compatible display on %s", path);
}
FF_DEBUG("Brightness DDC/CI: scan finished with %zu compatible bus(es)", cache->count);
return cache->count > 0;
}
static bool readEdid(int fd, uint8_t* edidData) {
uint8_t offset = 0;
struct iic_msg msgs[2];
msgs[0].slave = FF_DDC_EDID_WRITE_ADDR;
msgs[0].flags = IIC_M_WR;
msgs[0].len = 1;
msgs[0].buf = &offset;
msgs[1].slave = FF_DDC_EDID_WRITE_ADDR;
msgs[1].flags = IIC_M_RD;
msgs[1].len = 128;
msgs[1].buf = edidData;
struct iic_rdwr_data data = {
.msgs = msgs,
.nmsgs = 2
};
return ioctl(fd, I2CRDWR, &data) >= 0;
}
static bool detectCachedDevices(FFBrightnessOptions* options, FFlist* result, FFBrightnessDdcCache* cache) {
const size_t cachedCount = cache->count;
const uint32_t resultLength = result->length;
size_t validCount = 0;
FF_DEBUG("Brightness DDC/CI: validating %zu cached bus(es)", cachedCount);
for (size_t i = 0; i < cachedCount; ++i) {
const int device = cache->devices[i];
char path[32];
snprintf(path, sizeof(path), "/dev/iic%d", device);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: failed to open cached bus %d (errno=%d: %s)", device, error, strerror(error));
continue;
}
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
uint32_t current = ((uint32_t) response[8] << 8u) + (uint32_t) response[9];
uint32_t max = ((uint32_t) response[6] << 8u) + (uint32_t) response[7];
if (max == 0 || current > max) {
FF_DEBUG("Brightness DDC/CI: ignoring bus %d with invalid brightness range %u/%u", device, current, max);
continue;
}
cache->devices[validCount++] = device;
// Try to read EDID for monitor name
uint8_t edidData[128];
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(32);
if (readEdid(fd, edidData)) {
ffEdidGetName(edidData, &name);
}
if (name.length == 0) {
ffStrbufSetF(&name, "I2C-%d", device);
}
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
brightness->builtin = false;
ffStrbufInitMove(&brightness->name, &name);
FF_DEBUG("Brightness DDC/CI: added display '%s' from bus %d (%u/%u)", brightness->name.chars, device, current, max);
}
cache->count = validCount;
FF_DEBUG("Brightness DDC/CI: cache validation kept %zu bus(es), result count is %u", validCount, result->length);
return result->length > resultLength;
}
const char* detectWithDdcci(FFBrightnessOptions* options, FFlist* result) {
FFBrightnessDdcCache cache = {0};
FF_DEBUG("Brightness DDC/CI: detection started (result count=%u)", result->length);
if (!loadCacheFromFile(&cache)) {
if (!scanI2cDevices(options, &cache)) {
FF_DEBUG("Brightness DDC/CI: no compatible I2C device found during initial scan");
return "No I2C devices found";
}
}
if (!detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: cached detection found no usable display; rescanning");
if (!scanI2cDevices(options, &cache) || !detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: rescan found no compatible display");
return "No DDC/CI compatible displays found";
}
}
saveCacheToFile(&cache);
FF_DEBUG("Brightness DDC/CI: detection finished with %u result(s)", result->length);
return nullptr;
}
#else
@@ -138,10 +338,18 @@ const char* detectWithBacklight([[maybe_unused]] FFBrightnessOptions* options, [
#endif
const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
FF_DEBUG("Brightness: detection started (result count=%u)", result->length);
detectWithBacklight(options, result);
FF_DEBUG("Brightness: backlight detection finished (result count=%u)", result->length);
if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length == 0) {
FF_DEBUG("Brightness: trying DDC/CI because no backlight result was found");
detectWithDdcci(options, result);
} else if (options->ddcciSleep == FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) {
FF_DEBUG("Brightness: skipping DDC/CI because it is disabled");
} else {
FF_DEBUG("Brightness: skipping DDC/CI because backlight detection already found %u result(s)", result->length);
}
FF_DEBUG("Brightness: detection finished (result count=%u)", result->length);
return nullptr;
}
+285 -86
View File
@@ -2,9 +2,277 @@
#include "common/io.h"
#include "common/edidHelper.h"
#include "common/strutil.h"
#include "common/debug.h"
#include "common/time.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#if defined(__linux__)
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
// Try to load cache from file
static bool loadCacheFromFile(FFBrightnessDdcCache* cache) {
cache->count = 0;
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
bool valid = false;
FF_DEBUG("Brightness DDC/CI: loading I2C cache from %s", cacheDir.chars);
if (ffReadFileBuffer(cacheDir.chars, &cacheContent)) {
// Parse cache: first number is count, rest are device numbers
char* ptr = cacheContent.chars;
char* end = ptr + cacheContent.length;
// Read count
char* next;
long count = strtol(ptr, &next, 10);
if (next != ptr && count > 0 && count <= FF_I2C_DEVICE_CACHE_MAX) {
ptr = next;
// Read device numbers and reject truncated or trailing data.
size_t parsedCount = 0;
for (; parsedCount < (size_t) count && ptr < end; ++parsedCount) {
long dev = strtol(ptr, &next, 10);
if (next == ptr || dev < 0 || dev >= 100) {
break;
}
cache->devices[parsedCount] = (int) dev;
ptr = next;
}
while (ptr < end && isspace((unsigned char) *ptr)) {
++ptr;
}
if (parsedCount == (size_t) count && ptr == end) {
cache->count = parsedCount;
valid = true;
for (size_t i = 0; i < cache->count; ++i) {
char path[32];
snprintf(path, sizeof(path), "/dev/i2c-%d", cache->devices[i]);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("Brightness DDC/CI: cached bus %d is unavailable (errno=%d: %s)", cache->devices[i], errno, strerror(errno));
valid = false;
break;
}
}
}
}
}
if (!valid) {
FF_DEBUG("Brightness DDC/CI: I2C cache is missing or invalid");
cache->count = 0;
return false;
}
FF_DEBUG("Brightness DDC/CI: loaded %zu bus(es) from cache", cache->count);
return true;
}
// Save cache to file
static void saveCacheToFile(const FFBrightnessDdcCache* cache) {
if (cache->count == 0) {
return;
}
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateF("%zu", cache->count);
for (size_t i = 0; i < cache->count; i++) {
ffStrbufAppendF(&cacheContent, " %d", cache->devices[i]);
}
if (ffWriteFileBuffer(cacheDir.chars, &cacheContent)) {
FF_DEBUG("Brightness DDC/CI: saved %zu bus(es) to %s", cache->count, cacheDir.chars);
} else {
FF_DEBUG("Brightness DDC/CI: failed to save cache to %s", cacheDir.chars);
}
}
static bool readDdcCiResponse(int fd, FFBrightnessOptions* options, uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
uint8_t request[5];
ffDdcCiBuildGetVcpRequest(request);
// i2c_msg.addr is a 7-bit address; the 8-bit write/read addresses are
// used only for the DDC/CI checksum.
struct i2c_msg msg = {
.addr = FF_DDC_CI_ADDR,
.flags = 0,
.len = sizeof(request),
.buf = request
};
struct i2c_rdwr_ioctl_data data = {
.msgs = &msg,
.nmsgs = 1
};
int ioctlResult = ioctl(fd, I2C_RDWR, &data);
if (ioctlResult != 1) {
FF_DEBUG("Brightness DDC/CI: write request failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, errno, strerror(errno));
return false;
}
ffTimeSleep(options->ddcciSleep);
msg.flags = I2C_M_RD;
msg.len = FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE;
msg.buf = response;
ioctlResult = ioctl(fd, I2C_RDWR, &data);
if (ioctlResult != 1) {
FF_DEBUG("Brightness DDC/CI: read response failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, errno, strerror(errno));
return false;
}
if (!ffDdcCiValidateGetVcpResponse(response)) {
FF_DEBUG("Brightness DDC/CI: invalid response (addr=0x%02x, header=0x%02x, type=0x%02x, opcode=0x%02x, checksum=0x%02x)",
(unsigned) response[0], (unsigned) response[1], (unsigned) response[2], (unsigned) response[4], (unsigned) response[10]);
return false;
}
FF_DEBUG("Brightness DDC/CI: valid response (fd=%d, current=%u, max=%u)", fd,
((unsigned) response[8] << 8u) | (unsigned) response[9],
((unsigned) response[6] << 8u) | (unsigned) response[7]);
return true;
}
static bool scanI2cDevices(FFBrightnessOptions* options, FFBrightnessDdcCache* cache) {
FF_DEBUG("Brightness DDC/CI: scanning Linux I2C buses");
cache->count = 0;
for (int i = 0; i < 100 && cache->count < FF_I2C_DEVICE_CACHE_MAX; i++) {
char path[32];
snprintf(path, sizeof(path), "/dev/i2c-%d", i);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
continue;
}
FF_DEBUG("Brightness DDC/CI: probing %s", path);
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
cache->devices[cache->count++] = i;
FF_DEBUG("Brightness DDC/CI: found compatible display on %s", path);
}
FF_DEBUG("Brightness DDC/CI: scan finished with %zu compatible bus(es)", cache->count);
return cache->count > 0;
}
static bool readEdid(int fd, uint8_t* edidData) {
uint8_t offset = 0;
struct i2c_msg msgs[2];
msgs[0].addr = FF_DDC_EDID_ADDR;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &offset;
msgs[1].addr = FF_DDC_EDID_ADDR;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 128;
msgs[1].buf = edidData;
struct i2c_rdwr_ioctl_data data;
data.msgs = msgs;
data.nmsgs = 2;
return ioctl(fd, I2C_RDWR, &data) == 2;
}
static bool detectCachedDevices(FFBrightnessOptions* options, FFlist* result, FFBrightnessDdcCache* cache) {
const size_t cachedCount = cache->count;
const uint32_t resultLength = result->length;
size_t validCount = 0;
FF_DEBUG("Brightness DDC/CI: validating %zu cached bus(es)", cachedCount);
for (size_t i = 0; i < cachedCount; ++i) {
const int device = cache->devices[i];
char path[32];
snprintf(path, sizeof(path), "/dev/i2c-%d", device);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("Brightness DDC/CI: failed to open cached bus %d (errno=%d: %s)", device, errno, strerror(errno));
continue;
}
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
uint32_t current = ((uint32_t) response[8] << 8u) + (uint32_t) response[9];
uint32_t max = ((uint32_t) response[6] << 8u) + (uint32_t) response[7];
if (max == 0 || current > max) {
FF_DEBUG("Brightness DDC/CI: ignoring bus %d with invalid brightness range %u/%u", device, current, max);
continue;
}
cache->devices[validCount++] = device;
// Try to read EDID for monitor name
uint8_t edidData[128];
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(32);
if (readEdid(fd, edidData)) {
ffEdidGetName(edidData, &name);
}
if (name.length == 0) {
ffStrbufSetF(&name, "I2C-%d", device);
}
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
brightness->builtin = false;
ffStrbufInitMove(&brightness->name, &name);
FF_DEBUG("Brightness DDC/CI: added display '%s' from bus %d (%u/%u)", brightness->name.chars, device, current, max);
}
cache->count = validCount;
FF_DEBUG("Brightness DDC/CI: cache validation kept %zu bus(es), result count is %u", validCount, result->length);
return result->length > resultLength;
}
static const char* detectWithDdcciNative(FFBrightnessOptions* options, FFlist* result) {
FFBrightnessDdcCache cache = {0};
FF_DEBUG("Brightness DDC/CI: native detection started (result count=%u)", result->length);
if (!loadCacheFromFile(&cache)) {
if (!scanI2cDevices(options, &cache)) {
FF_DEBUG("Brightness DDC/CI: no compatible I2C device found during initial scan");
return "No I2C devices found";
}
}
if (!detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: cached detection found no usable display; rescanning");
// A cached bus can remain present after its monitor is unplugged or
// its number is reused. Rebuild the cache when no valid reply arrives.
if (!scanI2cDevices(options, &cache) || !detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: rescan found no compatible display");
return "No DDC/CI compatible displays found";
}
}
saveCacheToFile(&cache);
FF_DEBUG("Brightness DDC/CI: native detection finished with %u result(s)", result->length);
return nullptr;
}
#endif
static const char* detectWithBacklight(FFlist* result) {
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight
@@ -12,6 +280,7 @@ static const char* detectWithBacklight(FFlist* result) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir(backlightDirPath);
if (dirp == nullptr) {
FF_DEBUG("opendir(\"%s\") failed: %s", backlightDirPath, strerror(errno));
return "Failed to open `/sys/class/backlight/`";
}
@@ -75,100 +344,30 @@ static const char* detectWithBacklight(FFlist* result) {
return nullptr;
}
#ifdef FF_HAVE_DDCUTIL
#include "detection/displayserver/displayserver.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include <ddcutil_macros.h>
#include <ddcutil_c_api.h>
// Try to be compatible with ddcutil 2.0
#if DDCUTIL_VMAJOR >= 2
double ddca_set_default_sleep_multiplier(double multiplier); // ddcutil 1.4
#else
DDCA_Status ddca_init(const char* libopts, int syslog_level, int opts);
#endif
static const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
FF_LIBRARY_LOAD_MESSAGE(libddcutil, "libddcutil" FF_LIBRARY_EXTENSION, 5);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_display_info_list2)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_open_display2)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_any_vcp_value_using_explicit_type)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display)
#ifndef FF_DISABLE_DLOPEN
FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init)
if (ffddca_init) {
FF_SUPPRESS_IO();
// Ref: https://github.com/rockowitz/ddcutil/issues/344
if (ffddca_init(nullptr, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) {
return "ddca_init() failed";
}
} else {
FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_set_default_sleep_multiplier);
if (ffddca_set_default_sleep_multiplier) {
ffddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0);
}
libddcutil = nullptr; // Don't dlclose libddcutil. See https://github.com/rockowitz/ddcutil/issues/330
}
#else
#if DDCUTIL_VMAJOR >= 2
if (ddca_init(nullptr, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) {
return "ddca_init() failed";
}
#else
ddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0);
#endif
#endif
FF_AUTO_FREE DDCA_Display_Info_List* infoList = nullptr;
if (ffddca_get_display_info_list2(false, &infoList) < 0) {
return "ddca_get_display_info_list2(false, &infoList) failed";
}
if (infoList->ct == 0) {
return "No DDC/CI compatible displays found";
}
for (int index = 0; index < infoList->ct; ++index) {
const DDCA_Display_Info* display = &infoList->info[index];
DDCA_Display_Handle handle;
if (ffddca_open_display2(display->dref, false, &handle) >= 0) {
DDCA_Any_Vcp_Value* vcpValue = nullptr;
if (ffddca_get_any_vcp_value_using_explicit_type(handle, 0x10 /*brightness*/, DDCA_NON_TABLE_VCP_VALUE, &vcpValue) >= 0) {
assert(vcpValue->value_type == DDCA_NON_TABLE_VCP_VALUE);
int current = VALREC_CUR_VAL(vcpValue), max = VALREC_MAX_VAL(vcpValue);
ffddca_free_any_vcp_value(vcpValue);
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
ffStrbufInitS(&brightness->name, display->model_name);
}
ffddca_close_display(handle);
}
}
return nullptr;
}
#endif
#include "detection/displayserver/displayserver.h"
const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
FF_DEBUG("Brightness: detection started (result count=%u)", result->length);
detectWithBacklight(result);
FF_DEBUG("Brightness: backlight detection finished (result count=%u)", result->length);
#ifdef FF_HAVE_DDCUTIL
if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) {
const FFDisplayServerResult* displayServer = ffConnectDisplayServer();
if (result->length < displayServer->displays.length) {
detectWithDdcci(options, result);
}
}
FF_DEBUG("Brightness: trying native DDC/CI (result count=%u, display count=%u)", result->length, displayServer->displays.length);
#if defined(__linux__)
const char* nativeError = detectWithDdcciNative(options, result);
if (nativeError != nullptr) {
FF_DEBUG("Native I2C DDC/CI failed: %s", nativeError);
}
#endif
} else {
FF_DEBUG("Brightness: skipping DDC/CI because result count=%u meets display count=%u", result->length, displayServer->displays.length);
}
} else {
FF_DEBUG("Brightness: skipping DDC/CI because it is disabled");
}
FF_DEBUG("Brightness: detection finished (result count=%u)", result->length);
return nullptr;
}
@@ -1,4 +1,5 @@
#include "brightness.h"
#include "common/debug.h"
#include "common/io.h"
#include <dev/wscons/wsconsio.h>
@@ -15,6 +16,7 @@ const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FF
if (devfd < 0) {
if (errno == EACCES && i == '0') {
FF_DEBUG("open(%s) failed: %s", path, strerror(errno));
return "Permission denied when opening tty device";
}
if (errno == ENOENT) {
+8
View File
@@ -1,5 +1,6 @@
#include "btrfs.h"
#include "common/debug.h"
#include "common/io.h"
#include <fcntl.h>
@@ -8,11 +9,13 @@ enum { uuidLen = (uint32_t) __builtin_strlen("00000000-0000-0000-0000-0000000000
static const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) {
int subfd = openat(dfd, "devices", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(\"devices\") failed: %s", strerror(errno));
return "openat(\"/sys/fs/btrfs/UUID/devices\") == -1";
}
FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);
if (dirp == nullptr) {
FF_DEBUG("fdopendir(\"devices\") failed: %s", strerror(errno));
close(subfd);
return "fdopendir(\"/sys/fs/btrfs/UUID/devices\") == nullptr";
}
@@ -42,11 +45,13 @@ static const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buff
static const char* enumerateFeatures(FFBtrfsResult* item, int dfd) {
int subfd = openat(dfd, "features", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(\"features\") failed: %s", strerror(errno));
return "openat(\"/sys/fs/btrfs/UUID/features\") == -1";
}
FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);
if (dirp == nullptr) {
FF_DEBUG("fdopendir(\"features\") failed: %s", strerror(errno));
return "fdopendir(\"/sys/fs/btrfs/UUID/features\") == nullptr";
}
@@ -67,12 +72,14 @@ static const char* enumerateFeatures(FFBtrfsResult* item, int dfd) {
static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) {
FF_AUTO_CLOSE_FD int subfd = openat(dfd, "allocation", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(\"allocation\") failed: %s", strerror(errno));
return "openat(\"/sys/fs/btrfs/UUID/allocation\") == -1";
}
if (ffReadFileBufferRelative(subfd, "global_rsv_size", buffer)) {
item->globalReservationTotal = ffStrbufToUInt(buffer, 0);
} else {
FF_DEBUG("ffReadFileBuffer(\"global_rsv_size\") failed");
return "ffReadFileBuffer(\"/sys/fs/btrfs/UUID/allocation/global_rsv_size\") == nullptr";
}
@@ -124,6 +131,7 @@ static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buff
const char* ffDetectBtrfs(FFlist* result) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/fs/btrfs/");
if (dirp == nullptr) {
FF_DEBUG("opendir(\"/sys/fs/btrfs\") failed: %s", strerror(errno));
return "opendir(\"/sys/fs/btrfs\") == nullptr";
}
+3
View File
@@ -1,4 +1,5 @@
#include "camera.h"
#include "common/debug.h"
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraMetadata.h>
@@ -48,11 +49,13 @@ const char* ffDetectCamera(FFlist* result) {
// API 24, so linking it unconditionally in CMakeLists.txt is fine on every supported device.
ACameraManager* manager = ACameraManager_create();
if (!manager) {
FF_DEBUG("ACameraManager_create() failed");
return "ACameraManager_create() failed";
}
ACameraIdList* idList = nullptr;
if (ACameraManager_getCameraIdList(manager, &idList) != ACAMERA_OK || !idList) {
FF_DEBUG("ACameraManager_getCameraIdList() failed");
ACameraManager_delete(manager);
return "ACameraManager_getCameraIdList() failed";
}
+3
View File
@@ -1,8 +1,10 @@
#include "camera.h"
#include "common/debug.h"
#include "common/io.h"
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#if FF_HAVE_LINUX_VIDEODEV2
@@ -26,6 +28,7 @@ const char* ffDetectCamera(FFlist* result) {
if (errno == ENXIO) {
continue;
}
FF_DEBUG("Failed to open /dev/videoN: %s", strerror(errno));
return "Failed to open /dev/videoN";
}
+11 -4
View File
@@ -1,5 +1,6 @@
extern "C" {
#include "camera.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/windows/com.h"
}
@@ -22,20 +23,26 @@ extern "C" const char* ffDetectCamera([[maybe_unused]] FFlist* result) {
}
FF_AUTO_RELEASE_COM_OBJECT IMFAttributes* attrs = nullptr;
if (FAILED(ffMFCreateAttributes(&attrs, 1))) {
HRESULT hr = ffMFCreateAttributes(&attrs, 1);
if (FAILED(hr)) {
FF_DEBUG("MFCreateAttributes() failed: %s", ffDebugHResult(hr));
return "MFCreateAttributes() failed";
}
if (FAILED(attrs->SetGUID(
hr = attrs->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID))) {
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr)) {
FF_DEBUG("SetGUID(MF_*) failed: %s", ffDebugHResult(hr));
return "SetGUID(MF_*) failed";
}
IMFActivate** devices = nullptr;
uint32_t count;
if (FAILED(ffMFEnumDeviceSources(attrs, &devices, &count))) {
hr = ffMFEnumDeviceSources(attrs, &devices, &count);
if (FAILED(hr)) {
FF_DEBUG("MFEnumDeviceSources() failed: %s", ffDebugHResult(hr));
return "MFEnumDeviceSources() failed";
}
+4 -1
View File
@@ -1,4 +1,5 @@
#include "chassis.h"
#include "common/debug.h"
#include "detection/host/host.h"
const char* ffDetectChassis(FFChassisResult* result) {
@@ -11,7 +12,9 @@ const char* ffDetectChassis(FFChassisResult* result) {
.uuid = ffStrbufCreate(),
.vendor = ffStrbufCreate(),
};
if (ffDetectHost(&host) != nullptr) {
const char* hostError = ffDetectHost(&host);
if (hostError != nullptr) {
FF_DEBUG("ffDetectHost() failed: %s", hostError);
return "Failed to detect host";
}
+3
View File
@@ -1,4 +1,5 @@
#include "chassis.h"
#include "common/debug.h"
#include "common/smbios.h"
// 7.4
@@ -32,11 +33,13 @@ static_assert(offsetof(FFSmbiosSystemEnclosure, ContainedElements) == 0x15,
const char* ffDetectChassis(FFChassisResult* result) {
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
if (!smbiosTable) {
FF_DEBUG("System enclosure: failed to get SMBIOS data");
return "Failed to get SMBIOS data";
}
const FFSmbiosSystemEnclosure* data = (const FFSmbiosSystemEnclosure*) (*smbiosTable)[FF_SMBIOS_TYPE_SYSTEM_ENCLOSURE];
if (!data) {
FF_DEBUG("System enclosure: section is not found in SMBIOS data");
return "System enclosure is not found in SMBIOS data";
}

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