mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-29 11:10:59 +01:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b385f6ce8d | |||
| 957e0d08e2 | |||
| 6e2a25759d | |||
| 460a715901 | |||
| 9ac3822105 | |||
| e02e44f673 | |||
| 0f712b4eaf | |||
| 995d172d3e | |||
| 8102478f2f | |||
| 537ffac39c | |||
| 95bcc4da5b | |||
| 1b3594f600 | |||
| ae1288d087 | |||
| b201fd2db9 | |||
| 7f458b03e8 | |||
| 5f65515c3c | |||
| 3d0747b56d | |||
| 68c5dd31f5 | |||
| a57ebe992a | |||
| 54dcb1b6f9 | |||
| 39d667d1d5 | |||
| 7099964530 | |||
| ae4cbcbdba | |||
| cee27354ba | |||
| 109644ec77 | |||
| 782b7b8fd1 | |||
| a9bf53b4a1 | |||
| ab200636f1 | |||
| 4d8d6a5fe9 | |||
| ef853fe858 | |||
| 47fbfcb0e9 | |||
| c88ec3bcc3 | |||
| 2838dccbfd | |||
| f0d8c996e6 | |||
| 6ff7925f45 | |||
| ffbf4ec7ac | |||
| a3f46d9742 | |||
| 4fb57bb39b | |||
| cdbe8f54c6 | |||
| 80632920bb | |||
| 5246f149eb | |||
| f00a021e27 | |||
| 676dbcfee2 | |||
| 9801d89316 | |||
| fb52d32452 | |||
| 36dc82e926 | |||
| 7fdd1d8e93 |
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
[](https://formulae.brew.sh/formula/fastfetch#default)
|
||||
[](https://github.com/fastfetch-cli/fastfetch/releases)
|
||||
[](https://github.com/fastfetch-cli/fastfetch/releases)
|
||||
[](https://repology.org/project/fastfetch/versions)
|
||||
[](https://repology.org/project/fastfetch/versions)
|
||||
[](https://repology.amdmi3.ru/project/fastfetch/versions)
|
||||
[](https://repology.amdmi3.ru/project/fastfetch/versions)
|
||||
[](https://deepwiki.com/fastfetch-cli/fastfetch)
|
||||
[](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>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -630,6 +630,7 @@
|
||||
"cs": "Czech",
|
||||
"de": "German",
|
||||
"es": "Spanish",
|
||||
"fi": "Finnish",
|
||||
"fr": "French",
|
||||
"gl": "Galician",
|
||||
"he": "Hebrew",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 +0,0 @@
|
||||
../../logo/logo.svg
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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####";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user