mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-06 06:54:21 +01:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c9d9c0449 | |||
| cad01fd222 | |||
| 4fa8e8cdac | |||
| cb403d7d0c | |||
| 731c0044d8 | |||
| eeadbe2a54 | |||
| 475961f3bf | |||
| d548c9fdb7 | |||
| bf7ebf1aaa | |||
| 56f4dc00a2 | |||
| 71f68ce0c5 | |||
| a5fb1ea061 | |||
| 9e58c70745 | |||
| f7c27253af | |||
| 6c4ae07971 |
@@ -157,7 +157,7 @@ jobs:
|
|||||||
ls -l fastfetch
|
ls -l fastfetch
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-android-${{ inputs.abi }}
|
name: fastfetch-android-${{ inputs.abi }}
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: download benchmark data
|
- name: download benchmark data
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||||
with:
|
with:
|
||||||
pattern: benchmark-*
|
pattern: benchmark-*
|
||||||
path: benchmarks
|
path: benchmarks
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Start VM
|
- name: Start VM
|
||||||
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
|
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
|
||||||
with:
|
with:
|
||||||
operating_system: dragonflybsd
|
operating_system: dragonflybsd
|
||||||
version: '6.4.2'
|
version: '6.4.2'
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-dragonfly-amd64
|
name: fastfetch-dragonfly-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Start VM
|
- name: Start VM
|
||||||
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
|
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
|
||||||
with:
|
with:
|
||||||
operating_system: freebsd
|
operating_system: freebsd
|
||||||
version: '15.1'
|
version: '15.1'
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-freebsd-amd64
|
name: fastfetch-freebsd-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Start VM
|
- name: Start VM
|
||||||
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
|
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
|
||||||
with:
|
with:
|
||||||
operating_system: haiku
|
operating_system: haiku
|
||||||
version: 'r1beta6'
|
version: 'r1beta6'
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
run: uname -a
|
run: uname -a
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake llvm22_clang ninja pkgconfig python3.10 lua || pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_devel zlib_devel chafa_devel cmake llvm22_clang ninja pkgconfig python3.10 lua
|
run: pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_headers vulkan_loader_devel zlib_devel chafa_devel cmake llvm22_clang ninja pkgconfig python3.10 lua || pkgman install -y git dbus_devel mesa_devel libelf_devel imagemagick_devel opencl_headers ocl_icd_devel vulkan_headers vulkan_loader_devel zlib_devel chafa_devel cmake llvm22_clang ninja pkgconfig python3.10 lua
|
||||||
|
|
||||||
- name: CMake configuration
|
- name: CMake configuration
|
||||||
run: CC=clang CXX=clang++ cmake -GNinja -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 CXX=clang++ cmake -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-haiku-amd64
|
name: fastfetch-haiku-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
ctest --output-on-failure
|
ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-linux-armv7l
|
name: fastfetch-linux-armv7l
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
if: inputs.arch == 'amd64'
|
if: inputs.arch == 'amd64'
|
||||||
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||||
with:
|
with:
|
||||||
languages: c
|
languages: c
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
|
|
||||||
- name: perform CodeQL analysis
|
- name: perform CodeQL analysis
|
||||||
if: inputs.arch == 'amd64'
|
if: inputs.arch == 'amd64'
|
||||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||||
|
|
||||||
- name: list features
|
- name: list features
|
||||||
run: ./fastfetch --list-features
|
run: ./fastfetch --list-features
|
||||||
@@ -123,13 +123,13 @@ jobs:
|
|||||||
cpack -V
|
cpack -V
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-linux-${{ inputs.arch }}
|
name: fastfetch-linux-${{ inputs.arch }}
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|
||||||
- name: upload benchmark data
|
- name: upload benchmark data
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: benchmark-linux-${{ inputs.arch }}
|
name: benchmark-linux-${{ inputs.arch }}
|
||||||
path: benchmark.json
|
path: benchmark.json
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-linux-i686
|
name: fastfetch-linux-i686
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
'
|
'
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-linux-loong64
|
name: fastfetch-linux-loong64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
ctest --output-on-failure
|
ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-linux-${{ inputs.arch }}
|
name: fastfetch-linux-${{ inputs.arch }}
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -74,13 +74,13 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-macos-universal
|
name: fastfetch-macos-universal
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|
||||||
- name: upload benchmark data
|
- name: upload benchmark data
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: benchmark-macos-universal
|
name: benchmark-macos-universal
|
||||||
path: benchmark.json
|
path: benchmark.json
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
shell: alpine.sh {0}
|
shell: alpine.sh {0}
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-musl-amd64
|
name: fastfetch-musl-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Start VM
|
- name: Start VM
|
||||||
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
|
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
|
||||||
with:
|
with:
|
||||||
operating_system: netbsd
|
operating_system: netbsd
|
||||||
version: '10.1'
|
version: '11.0'
|
||||||
shell: bash
|
shell: bash
|
||||||
cpu_count: 4
|
cpu_count: 4
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-netbsd-amd64
|
name: fastfetch-netbsd-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Start VM
|
- name: Start VM
|
||||||
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
|
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
|
||||||
with:
|
with:
|
||||||
operating_system: omnios
|
operating_system: omnios
|
||||||
version: 'r151058'
|
version: 'r151058'
|
||||||
@@ -64,7 +64,7 @@ jobs:
|
|||||||
cpack -V
|
cpack -V
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-omnios-amd64
|
name: fastfetch-omnios-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Start VM
|
- name: Start VM
|
||||||
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
|
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
|
||||||
with:
|
with:
|
||||||
operating_system: openbsd
|
operating_system: openbsd
|
||||||
version: '7.9'
|
version: '8.0'
|
||||||
shell: bash
|
shell: bash
|
||||||
cpu_count: 4
|
cpu_count: 4
|
||||||
|
|
||||||
@@ -32,10 +32,10 @@ jobs:
|
|||||||
- name: Update and install dependencies
|
- name: Update and install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo pkg_add -u
|
sudo pkg_add -u
|
||||||
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
|
sudo pkg_add -r llvm-22.1.8p10 cmake git wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 imagemagick chafa lua-5.4.8p2 # pkg-config is preinstalled
|
||||||
|
|
||||||
- name: CMake configuration
|
- 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' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
|
run: CC=clang-22 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
|
- name: CMake build
|
||||||
run: cmake --build . --target package --verbose -j4
|
run: cmake --build . --target package --verbose -j4
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
run: ctest --output-on-failure
|
run: ctest --output-on-failure
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-openbsd-amd64
|
name: fastfetch-openbsd-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
|
|
||||||
- name: download artifacts
|
- name: download artifacts
|
||||||
if: inputs.ffversion != steps.get_version_release.outputs.release
|
if: inputs.ffversion != steps.get_version_release.outputs.release
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||||
|
|
||||||
- name: create release
|
- name: create release
|
||||||
if: inputs.ffversion != steps.get_version_release.outputs.release
|
if: inputs.ffversion != steps.get_version_release.outputs.release
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: run VM
|
- name: run VM
|
||||||
uses: vmactions/solaris-vm@87d3c436511cef802cd1637f86f73b1a97715c8c # v1.4.1
|
uses: vmactions/solaris-vm@1c166978f5a2c1f87f1c9d4d8dd85ae3d1eff6f7 # v1.4.2
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
envs: 'CMAKE_BUILD_TYPE'
|
envs: 'CMAKE_BUILD_TYPE'
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
cpack -V
|
cpack -V
|
||||||
|
|
||||||
- name: upload artifacts
|
- name: upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-solaris-amd64
|
name: fastfetch-solaris-amd64
|
||||||
path: ./fastfetch-*.*
|
path: ./fastfetch-*.*
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ jobs:
|
|||||||
- if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'
|
- if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'
|
||||||
id: upload-unsigned-artifact
|
id: upload-unsigned-artifact
|
||||||
name: upload artifacts for signing
|
name: upload artifacts for signing
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-windows-${{ inputs.arch }}
|
name: fastfetch-windows-${{ inputs.arch }}
|
||||||
path: |
|
path: |
|
||||||
@@ -134,14 +134,14 @@ jobs:
|
|||||||
run: 7z a -t7z -mx9 -bd -y fastfetch-windows-${{ inputs.arch }}.7z LICENSE *.dll fastfetch.exe flashfetch.exe presets
|
run: 7z a -t7z -mx9 -bd -y fastfetch-windows-${{ inputs.arch }}.7z LICENSE *.dll fastfetch.exe flashfetch.exe presets
|
||||||
|
|
||||||
- name: upload true artifacts
|
- name: upload true artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: fastfetch-windows-${{ inputs.arch }}
|
name: fastfetch-windows-${{ inputs.arch }}
|
||||||
path: ./fastfetch-windows-${{ inputs.arch }}.*
|
path: ./fastfetch-windows-${{ inputs.arch }}.*
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
- name: upload benchmark data
|
- name: upload benchmark data
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: benchmark-windows-${{ inputs.arch }}
|
name: benchmark-windows-${{ inputs.arch }}
|
||||||
path: benchmark.json
|
path: benchmark.json
|
||||||
|
|||||||
+12
-2
@@ -1,9 +1,14 @@
|
|||||||
# Unreleased
|
# 2.71.0
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
* Added display detection through the `org.gnome.Mutter.DisplayConfig` D-Bus interface on GNOME and Cinnamon Wayland sessions, which reports the primary monitor and the raw EDID directly instead of matching the `wl_output` name against the DRM connectors. A D-Bus enabled build is required. (Display, Linux)
|
* Added a `spin` logo type (`--logo-type spin`), which animates the built-in logo or a text file as a 3D slab that rotates. (Logo)
|
||||||
|
* This logo type requires `--watch <s>`/`--dynamic-interval <ms>` being set to enable animation. For example: `fastfetch --logo-type spin --watch 1` will animate the logo every second.
|
||||||
|
* `--logo-spin-speed-per-frame` sets the degrees per drawn frame (default 10); `--logo-spin-speed-per-round` sets the seconds one revolution takes and wins when both are given. Both accept a negative value to turn the other way.
|
||||||
|
* `--logo-spin-angle` picks the frame to start from, and is the only frame drawn without `--watch`.
|
||||||
|
* Optimised display detection on GNOME and Cinnamon Wayland sessions. (Display, Linux)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
* Don't detect mise and atuin as shells. (#2686, Shell)
|
||||||
* Rejected an empty `separator.string` in the JSON config with an error message. (#2689, Separator)
|
* Rejected an empty `separator.string` in the JSON config with an error message. (#2689, Separator)
|
||||||
* Fixed fastfetch blocking forever when the terminal answered a terminal query only partially, for example with just one of the two OSC 10/11 color replies. (Logo / Terminal / TerminalTheme, Unix / Windows)
|
* Fixed fastfetch blocking forever when the terminal answered a terminal query only partially, for example with just one of the two OSC 10/11 color replies. (Logo / Terminal / TerminalTheme, Unix / Windows)
|
||||||
* Fixed a `$` ending a logo line or the logo itself being parsed as the start of a color code instead of a plain dollar sign. (#2666, Logo)
|
* Fixed a `$` ending a logo line or the logo itself being parsed as the start of a color code instead of a plain dollar sign. (#2666, Logo)
|
||||||
@@ -13,9 +18,14 @@ Bugfixes:
|
|||||||
* Fixed a skipped `{?}` / `{/}` conditional in a format string ending at the `{?}` / `{/}` of a conditional nested in it. (General)
|
* Fixed a skipped `{?}` / `{/}` conditional in a format string ending at the `{?}` / `{/}` of a conditional nested in it. (General)
|
||||||
* Fixed numbers in format strings that don't fit in 32 bits wrapping around to a valid argument index, constant index, substring index or length, e.g. `{4294967297}` being read as `{1}`, and a signed integer overflow for `{1:-2147483648}`. (General)
|
* Fixed numbers in format strings that don't fit in 32 bits wrapping around to a valid argument index, constant index, substring index or length, e.g. `{4294967297}` being read as `{1}`, and a signed integer overflow for `{1:-2147483648}`. (General)
|
||||||
* Fixed substring formatting printing the placeholder as is, instead of nothing, when the start index is outside of the string and an end index is given. (General)
|
* Fixed substring formatting printing the placeholder as is, instead of nothing, when the start index is outside of the string and an end index is given. (General)
|
||||||
|
* Fixed the CPU micro-architecture level (`march`) being under-reported on Windows on ARM when `IsProcessorFeaturePresent` does not expose a feature the CPU implements. (CPU, Windows)
|
||||||
|
* It is now decoded from the AArch64 ID registers in the registry, matching the Linux detection.
|
||||||
|
|
||||||
Logos:
|
Logos:
|
||||||
* Added Tiny Core Linux (#2655)
|
* Added Tiny Core Linux (#2655)
|
||||||
|
* Added ArmadaOS (#2649)
|
||||||
|
* Added AstrOS (#2582)
|
||||||
|
* Added openSUSE Leap Micro (#2703)
|
||||||
|
|
||||||
# 2.70.0
|
# 2.70.0
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
|
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
|
||||||
|
|
||||||
project(fastfetch
|
project(fastfetch
|
||||||
VERSION 2.70.0
|
VERSION 2.71.0
|
||||||
LANGUAGES C
|
LANGUAGES C
|
||||||
DESCRIPTION "Fast neofetch-like system information tool"
|
DESCRIPTION "Fast neofetch-like system information tool"
|
||||||
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
|
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
|
||||||
@@ -564,6 +564,8 @@ set(LIBFASTFETCH_SRC
|
|||||||
src/detection/zpool/zpool.c
|
src/detection/zpool/zpool.c
|
||||||
src/logo/builtin.c
|
src/logo/builtin.c
|
||||||
src/logo/logo.c
|
src/logo/logo.c
|
||||||
|
src/logo/spin/render.c
|
||||||
|
src/logo/spin/spin.c
|
||||||
src/modules/modules.c
|
src/modules/modules.c
|
||||||
src/options/display.c
|
src/options/display.c
|
||||||
src/options/logo.c
|
src/options/logo.c
|
||||||
@@ -2368,6 +2370,13 @@ if (BUILD_TESTS)
|
|||||||
PRIVATE libfastfetch
|
PRIVATE libfastfetch
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_executable(fastfetch-test-spin
|
||||||
|
tests/spin.c
|
||||||
|
)
|
||||||
|
target_link_libraries(fastfetch-test-spin
|
||||||
|
PRIVATE libfastfetch
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(fastfetch-test-strbuf
|
add_executable(fastfetch-test-strbuf
|
||||||
tests/strbuf.c
|
tests/strbuf.c
|
||||||
)
|
)
|
||||||
@@ -2490,6 +2499,7 @@ if (BUILD_TESTS)
|
|||||||
enable_testing()
|
enable_testing()
|
||||||
add_test(NAME test-cache COMMAND fastfetch-test-cache)
|
add_test(NAME test-cache COMMAND fastfetch-test-cache)
|
||||||
add_test(NAME test-logo COMMAND fastfetch-test-logo)
|
add_test(NAME test-logo COMMAND fastfetch-test-logo)
|
||||||
|
add_test(NAME test-spin COMMAND fastfetch-test-spin)
|
||||||
add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf)
|
add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf)
|
||||||
add_test(NAME test-list COMMAND fastfetch-test-list)
|
add_test(NAME test-list COMMAND fastfetch-test-list)
|
||||||
add_test(NAME test-format COMMAND fastfetch-test-format)
|
add_test(NAME test-format COMMAND fastfetch-test-format)
|
||||||
|
|||||||
@@ -213,10 +213,6 @@ Alternatively, you may try using `fastfetch --gpu-driver-specific`, which will m
|
|||||||
|
|
||||||
Try `export XAUTHORITY=$HOME/.Xauthority`
|
Try `export XAUTHORITY=$HOME/.Xauthority`
|
||||||
|
|
||||||
### Q: Fastfetch cannot detect my awesome 3rd-party macOS window manager!
|
|
||||||
|
|
||||||
Try `fastfetch --wm-detect-plugin`. See also [#984](https://github.com/fastfetch-cli/fastfetch/issues/984)
|
|
||||||
|
|
||||||
### Q: How can I change the colors of my ASCII logo?
|
### Q: How can I change the colors of my ASCII logo?
|
||||||
|
|
||||||
Try `fastfetch --logo-color-[1-9] <color>`, where `[1-9]` is the index of color placeholders.
|
Try `fastfetch --logo-color-[1-9] <color>`, where `[1-9]` is the index of color placeholders.
|
||||||
|
|||||||
@@ -203,10 +203,47 @@
|
|||||||
"iterm": "Image file, rendered using the iTerm graphics protocol",
|
"iterm": "Image file, rendered using the iTerm graphics protocol",
|
||||||
"chafa": "Image file, printed as ASCII art using libchafa",
|
"chafa": "Image file, printed as ASCII art using libchafa",
|
||||||
"raw": "Image file, printed as raw binary string",
|
"raw": "Image file, printed as raw binary string",
|
||||||
|
"spin": "Built-in logo (or a text file) drawn as a 3D slab; it turns one step per refresh with `--watch`",
|
||||||
"none": "Disable logo printing"
|
"none": "Disable logo printing"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"long": "logo-spin-speed-per-frame",
|
||||||
|
"desc": "Set how far the spin logo turns per drawn frame, in degrees",
|
||||||
|
"remark": "Only used by `--logo-type spin`. One revolution takes 360 divided by this value frames, so `--watch` sets the pace; negative turns the other way. 0 means not set, and the default is 10",
|
||||||
|
"arg": {
|
||||||
|
"type": "num",
|
||||||
|
"default": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long": "logo-spin-speed-per-round",
|
||||||
|
"desc": "Set how many seconds one revolution of the spin logo takes",
|
||||||
|
"remark": "Only used by `--logo-type spin`. Read from the clock, so a slow refresh turns further instead of slowing the logo down; negative turns the other way. 0 means not set, and this wins over `--logo-spin-speed-per-frame` when both are given",
|
||||||
|
"arg": {
|
||||||
|
"type": "num",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long": "logo-spin-tilt",
|
||||||
|
"desc": "Set the camera tilt of the spin logo, in degrees",
|
||||||
|
"remark": "Only used by `--logo-type spin`",
|
||||||
|
"arg": {
|
||||||
|
"type": "num",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"long": "logo-spin-angle",
|
||||||
|
"desc": "Set the rotation of the first spin logo frame, in degrees",
|
||||||
|
"remark": "Only used by `--logo-type spin`. Without `--watch` the logo is drawn at this angle and stays there. 360 is the same as 0",
|
||||||
|
"arg": {
|
||||||
|
"type": "num",
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"long": "logo-width",
|
"long": "logo-width",
|
||||||
"desc": "Set the width of the logo (in characters) if it is an image",
|
"desc": "Set the width of the logo (in characters) if it is an image",
|
||||||
|
|||||||
+47
-1
@@ -652,6 +652,10 @@
|
|||||||
"const": "raw",
|
"const": "raw",
|
||||||
"description": "Image file rendered as a raw binary string"
|
"description": "Image file rendered as a raw binary string"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"const": "spin",
|
||||||
|
"description": "Built-in logo (or a text file) drawn as a 3D slab, turning one step per `--watch` refresh"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"const": "none",
|
"const": "none",
|
||||||
"description": "Disable logo printing"
|
"description": "Disable logo printing"
|
||||||
@@ -661,7 +665,49 @@
|
|||||||
},
|
},
|
||||||
"source": {
|
"source": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Path to the logo source file"
|
"description": "Path to the logo source file. For the spin logo: `auto` (the detected OS), any built-in logo name, or a text file in fastfetch logo syntax"
|
||||||
|
},
|
||||||
|
"spin": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Options of the spin logo (`logo.type: spin`)",
|
||||||
|
"properties": {
|
||||||
|
"speed": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "How fast the logo turns. 0 means not set for either key; `perRound` wins over `perFrame` when both are given",
|
||||||
|
"properties": {
|
||||||
|
"perFrame": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Degrees per drawn frame; a revolution takes 360 / perFrame frames, negative turns the other way. 0 means not set, and 10 is used when neither speed is set",
|
||||||
|
"minimum": -360,
|
||||||
|
"maximum": 360,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"perRound": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Seconds one revolution takes, measured on the clock, negative turns the other way",
|
||||||
|
"minimum": -3600,
|
||||||
|
"maximum": 3600,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tilt": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Camera tilt in degrees",
|
||||||
|
"minimum": -80,
|
||||||
|
"maximum": 80,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"angle": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Rotation of the first frame in degrees; 360 is the same as 0. Without `--watch` this is the only frame drawn",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 360,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@@ -175,27 +175,6 @@ typedef struct FFAndroidLabelPool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[gnu::nonnull(1)]] static void appendCodePoint(FFstrbuf* result, uint32_t code) {
|
|
||||||
char encoded[4] = {};
|
|
||||||
uint32_t length = 0;
|
|
||||||
if (code < 0x80) {
|
|
||||||
encoded[length++] = (char) code;
|
|
||||||
} else if (code < 0x800) {
|
|
||||||
encoded[length++] = (char) (0xC0 | (code >> 6));
|
|
||||||
encoded[length++] = (char) (0x80 | (code & 0x3F));
|
|
||||||
} else if (code < 0x10000) {
|
|
||||||
encoded[length++] = (char) (0xE0 | (code >> 12));
|
|
||||||
encoded[length++] = (char) (0x80 | ((code >> 6) & 0x3F));
|
|
||||||
encoded[length++] = (char) (0x80 | (code & 0x3F));
|
|
||||||
} else {
|
|
||||||
encoded[length++] = (char) (0xF0 | (code >> 18));
|
|
||||||
encoded[length++] = (char) (0x80 | ((code >> 12) & 0x3F));
|
|
||||||
encoded[length++] = (char) (0x80 | ((code >> 6) & 0x3F));
|
|
||||||
encoded[length++] = (char) (0x80 | (code & 0x3F));
|
|
||||||
}
|
|
||||||
ffStrbufAppendNS(result, length, encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// One string out of a pool.
|
// One string out of a pool.
|
||||||
//
|
//
|
||||||
// The two lengths in front of a UTF-8 string are a *character* count and a *byte* count, and they
|
// The two lengths in front of a UTF-8 string are a *character* count and a *byte* count, and they
|
||||||
@@ -250,7 +229,7 @@ typedef struct FFAndroidLabelPool {
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appendCodePoint(result, code);
|
ffStrbufAppendUtf32CodePoint(result, code);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ static FFBluetoothResult* findByName(FFlist* devices, const char* name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const char* detectLe(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */, NSDictionary<NSString*, NSString*>* classicAddressByIdentifier) {
|
static const char* detectLe(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */, NSDictionary<NSString*, NSString*>* classicAddressByIdentifier) {
|
||||||
|
#ifdef MAC_OS_X_VERSION_10_15
|
||||||
CBManagerAuthorization authorization = CBCentralManager.authorization;
|
CBManagerAuthorization authorization = CBCentralManager.authorization;
|
||||||
if (authorization == CBManagerAuthorizationDenied || authorization == CBManagerAuthorizationRestricted) {
|
if (authorization == CBManagerAuthorizationDenied || authorization == CBManagerAuthorizationRestricted) {
|
||||||
return nullptr; // The user said no. `NotDetermined` is left to the manager below, which asks.
|
return nullptr; // The user said no. `NotDetermined` is left to the manager below, which asks.
|
||||||
@@ -377,6 +378,7 @@ static const char* detectLe(FFBluetoothOptions* options, FFlist* devices /* FFBl
|
|||||||
target->signalQuality = ffRssiToSignalQuality(rssi.intValue);
|
target->signalQuality = ffRssiToSignalQuality(rssi.intValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
+226
-55
@@ -411,81 +411,246 @@ void ffCPUDetectByCpuid(FFCPUResult* cpu) {
|
|||||||
#undef HAS_CAP
|
#undef HAS_CAP
|
||||||
}
|
}
|
||||||
#elif _WIN32
|
#elif _WIN32
|
||||||
#include <processthreadsapi.h>
|
#include "common/windows/registry.h"
|
||||||
|
|
||||||
// Missing from winnt.h of MinGW-w64
|
// Windows exposes the AArch64 ID registers in the registry, as values named
|
||||||
#define PF_ARM_LSE2_AVAILABLE 62
|
// "CP xxxx" under HARDWARE\DESCRIPTION\System\CentralProcessor\0. Each value is
|
||||||
#define PF_RESERVED_FEATURE 63
|
// the 64-bit content of one ID register; decode the same fields that the Linux
|
||||||
#define PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE 64
|
// HWCAP based detection below is built from.
|
||||||
#define PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE 65
|
//
|
||||||
#define PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE 66
|
// Where "xxxx" comes from
|
||||||
#define PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE 67
|
// -----------------------
|
||||||
#define PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE 68
|
// The four hex digits encode the AArch64 system register name S3_0_C0_CRm_op2
|
||||||
#define PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE 69
|
// (all ID registers live in CRn = C0 and use op0 = 3):
|
||||||
#define PF_ARM_SME_INSTRUCTIONS_AVAILABLE 70
|
//
|
||||||
#define PF_ARM_SME2_INSTRUCTIONS_AVAILABLE 71
|
// xxxx = 0x4000 + (CRm << 3) + op2
|
||||||
#define PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE 72
|
//
|
||||||
#define PF_ARM_SME2_2_INSTRUCTIONS_AVAILABLE 73
|
// e.g. ID_AA64PFR0_EL1 = S3_0_C0_C4_0 -> 0x4000 + (4 << 3) + 0 = 0x4020,
|
||||||
#define PF_ARM_SME_AES_INSTRUCTIONS_AVAILABLE 74
|
// ID_AA64ISAR1_EL1 = S3_0_C0_C6_1 -> 0x4000 + (6 << 3) + 1 = 0x4031.
|
||||||
#define PF_ARM_SME_SBITPERM_INSTRUCTIONS_AVAILABLE 75
|
// The full list is in the ARM Architecture Reference Manual, chapter "AArch64
|
||||||
#define PF_ARM_SME_SF8MM4_INSTRUCTIONS_AVAILABLE 76
|
// System Register Encoding". Linux has the same encodings in sysreg.h as
|
||||||
#define PF_ARM_SME_SF8MM8_INSTRUCTIONS_AVAILABLE 77
|
// SYS_ID_AA64*_EL1; Windows just prints the encoding instead of the name.
|
||||||
#define PF_ARM_SME_SF8DP2_INSTRUCTIONS_AVAILABLE 78
|
//
|
||||||
#define PF_ARM_SME_SF8DP4_INSTRUCTIONS_AVAILABLE 79
|
// Where the offsets / minimums come from
|
||||||
#define PF_ARM_SME_SF8FMA_INSTRUCTIONS_AVAILABLE 80
|
// --------------------------------------
|
||||||
#define PF_ARM_SME_F8F32_INSTRUCTIONS_AVAILABLE 81
|
// Every field offset below is the LSB of that field in the ARM ARM description
|
||||||
#define PF_ARM_SME_F8F16_INSTRUCTIONS_AVAILABLE 82
|
// of the ID register (also SYS_ID_AA64*_EL1_*_SHIFT in Linux sysreg.h).
|
||||||
#define PF_ARM_SME_F16F16_INSTRUCTIONS_AVAILABLE 83
|
// Every minimum is the lowest field value that guarantees the corresponding
|
||||||
#define PF_ARM_SME_B16B16_INSTRUCTIONS_AVAILABLE 84
|
// instruction/feature (the field value tables in the ARM ARM, mirrored by the
|
||||||
#define PF_ARM_SME_F64F64_INSTRUCTIONS_AVAILABLE 85
|
// cpufeature.c checks that set the matching HWCAP/HWCAP2 bit):
|
||||||
#define PF_ARM_SME_I16I64_INSTRUCTIONS_AVAILABLE 86
|
//
|
||||||
#define PF_ARM_SME_LUTv2_INSTRUCTIONS_AVAILABLE 87
|
// value 0b0000 baseline value; usually "not implemented", but for fields
|
||||||
#define PF_ARM_SME_FA64_INSTRUCTIONS_AVAILABLE 88
|
// that describe an always-present form it names that form
|
||||||
|
// (FP/AdvSIMD: implemented; SVEver: SVE; SMEver: SME)
|
||||||
|
// value 0b0001 first extension level for most fields; SVEver 1 is SVE2
|
||||||
|
// value 0b0010 second level; ATOMIC, LRCPC, DPB, FlagM, PFR1.SME, CSSC
|
||||||
|
// and SMFR0.SMEver use >= 2 for their newer form
|
||||||
|
// value 0b1111 reserved "not implemented" marker; a field holding 0xF
|
||||||
|
// must never be treated as supporting the feature.
|
||||||
|
//
|
||||||
|
// armField() therefore requires both "field != 0xF" and
|
||||||
|
// "field >= minimum". minimum == 0 is used only where the mere presence of an
|
||||||
|
// implemented field is enough (PFR0.FP and PFR0.AdvSIMD for the ARMv8-A
|
||||||
|
// baseline). A register can be missing on some Windows builds/CPUs; a missing
|
||||||
|
// register makes all of its features false and thus lowers the reported
|
||||||
|
// version instead of reporting a wrong higher one.
|
||||||
|
//
|
||||||
|
// Not every field is a 4-bit value: ID_AA64FPFR0_EL1 (ARMv9.5 FP8) only
|
||||||
|
// defines individual feature bits, so those checks use armBit().
|
||||||
|
//
|
||||||
|
// To add a new instruction/feature in the future
|
||||||
|
// ----------------------------------------------
|
||||||
|
// 1. Find the ID register and field that advertises it in the ARM ARM (and in
|
||||||
|
// the matching Linux HWCAP/HWCAP2 definition).
|
||||||
|
// 2. If that register is not read yet, add an FF_ARM_REG_* entry before
|
||||||
|
// FF_ARM_REG_COUNT and its "CP xxxx" name in armRegisterNames[]; the xxxx is
|
||||||
|
// computed with the formula above. Registers are read automatically because
|
||||||
|
// armReadRegisters() loops over the whole table.
|
||||||
|
// 3. Add a bool using armField(regs, present, FF_ARM_REG_..., offset, minimum)
|
||||||
|
// or armBit(regs, present, FF_ARM_REG_..., bit). Put the register/field name
|
||||||
|
// and the Linux HWCAP bit it mirrors in a comment.
|
||||||
|
// 4. Insert the condition into the correct version's place in the if/else if
|
||||||
|
// chain below, keeping the order identical to the Linux implementation so
|
||||||
|
// both platforms report the same march. A newly required feature belongs to
|
||||||
|
// the version that introduced it; a feature that only raises the baseline
|
||||||
|
// is an extra `else if` above the older checks.
|
||||||
|
// 5. Rebuild and check with `fastfetch -s cpu --format json`; compare against
|
||||||
|
// Linux on the same machine when possible.
|
||||||
|
|
||||||
|
typedef enum FFCPUArmRegister {
|
||||||
|
FF_ARM_REG_PFR0,
|
||||||
|
FF_ARM_REG_PFR1,
|
||||||
|
FF_ARM_REG_ZFR0,
|
||||||
|
FF_ARM_REG_SMFR0,
|
||||||
|
FF_ARM_REG_FPFR0,
|
||||||
|
FF_ARM_REG_ISAR0,
|
||||||
|
FF_ARM_REG_ISAR1,
|
||||||
|
FF_ARM_REG_ISAR2,
|
||||||
|
FF_ARM_REG_ISAR3,
|
||||||
|
FF_ARM_REG_MMFR1,
|
||||||
|
FF_ARM_REG_COUNT,
|
||||||
|
} FFCPUArmRegister;
|
||||||
|
|
||||||
|
static const wchar_t* const armRegisterNames[FF_ARM_REG_COUNT] = {
|
||||||
|
[FF_ARM_REG_PFR0] = L"CP 4020", // ID_AA64PFR0_EL1
|
||||||
|
[FF_ARM_REG_PFR1] = L"CP 4021", // ID_AA64PFR1_EL1
|
||||||
|
[FF_ARM_REG_ZFR0] = L"CP 4024", // ID_AA64ZFR0_EL1
|
||||||
|
[FF_ARM_REG_SMFR0] = L"CP 4025", // ID_AA64SMFR0_EL1
|
||||||
|
[FF_ARM_REG_FPFR0] = L"CP 4027", // ID_AA64FPFR0_EL1
|
||||||
|
[FF_ARM_REG_ISAR0] = L"CP 4030", // ID_AA64ISAR0_EL1
|
||||||
|
[FF_ARM_REG_ISAR1] = L"CP 4031", // ID_AA64ISAR1_EL1
|
||||||
|
[FF_ARM_REG_ISAR2] = L"CP 4032", // ID_AA64ISAR2_EL1
|
||||||
|
[FF_ARM_REG_ISAR3] = L"CP 4033", // ID_AA64ISAR3_EL1
|
||||||
|
[FF_ARM_REG_MMFR1] = L"CP 4039", // ID_AA64MMFR1_EL1
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool armReadRegisters(uint64_t regs[FF_ARM_REG_COUNT], bool present[FF_ARM_REG_COUNT]) {
|
||||||
|
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
|
||||||
|
if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey, nullptr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool anyData = false;
|
||||||
|
for (uint32_t i = 0; i < FF_ARM_REG_COUNT; ++i) {
|
||||||
|
uint64_t value = 0;
|
||||||
|
if (ffRegReadUint64(hKey, armRegisterNames[i], &value, nullptr)) {
|
||||||
|
regs[i] = value;
|
||||||
|
present[i] = true;
|
||||||
|
anyData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return anyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same rule as the Linux implementation: a 4-bit field is present when it is
|
||||||
|
// neither 0xF (not implemented) nor below the value required by the feature.
|
||||||
|
// `minimum` is the ARM ARM field value that first guarantees the feature; see
|
||||||
|
// the magic number explanation above.
|
||||||
|
static bool armField(
|
||||||
|
const uint64_t regs[FF_ARM_REG_COUNT],
|
||||||
|
const bool present[FF_ARM_REG_COUNT],
|
||||||
|
FFCPUArmRegister reg,
|
||||||
|
uint8_t offset,
|
||||||
|
uint8_t minimum) {
|
||||||
|
if (!present[reg]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t value = (uint8_t) ((regs[reg] >> offset) & 0xF);
|
||||||
|
return value != 0xF && value >= minimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single-bit feature fields (currently only ID_AA64FPFR0_EL1). Prefer
|
||||||
|
// armField() for the normal 4-bit ID fields.
|
||||||
|
static bool armBit(
|
||||||
|
const uint64_t regs[FF_ARM_REG_COUNT],
|
||||||
|
const bool present[FF_ARM_REG_COUNT],
|
||||||
|
FFCPUArmRegister reg,
|
||||||
|
uint8_t bit) {
|
||||||
|
return present[reg] && ((regs[reg] >> bit) & 1) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ffCPUDetectByCpuid(FFCPUResult* cpu) {
|
void ffCPUDetectByCpuid(FFCPUResult* cpu) {
|
||||||
|
uint64_t regs[FF_ARM_REG_COUNT] = {};
|
||||||
|
bool present[FF_ARM_REG_COUNT] = {};
|
||||||
|
|
||||||
|
if (!armReadRegisters(regs, present)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID_AA64ISAR1_EL1.APA [7:4] describes QARMA5 address authentication,
|
||||||
|
// while API [11:8] describes the implementation-defined variant. They are
|
||||||
|
// mutually exclusive but share the same encoding. Normalize API into the
|
||||||
|
// APA slot so both implementations are detected (Apple Silicon reports
|
||||||
|
// APA = 0 and API = 2).
|
||||||
|
uint64_t isar1 = regs[FF_ARM_REG_ISAR1];
|
||||||
|
uint8_t apa = (uint8_t) ((isar1 >> 4) & 0xF);
|
||||||
|
uint8_t api = (uint8_t) ((isar1 >> 8) & 0xF);
|
||||||
|
if (apa == 0 && api != 0) {
|
||||||
|
isar1 = (isar1 & ~(UINT64_C(0xF) << 4)) | ((uint64_t) api << 4);
|
||||||
|
regs[FF_ARM_REG_ISAR1] = isar1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu->march = "unknown";
|
||||||
|
|
||||||
// ARMv8-A
|
// ARMv8-A
|
||||||
bool has_vfp = IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE); // Implies basic FP support
|
bool has_fp = armField(regs, present, FF_ARM_REG_PFR0, 16, 0); // PFR0.FP[19:16] implemented -> HWCAP_FP
|
||||||
bool has_neon = IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE); // NEON (ASIMD)
|
bool has_asimd = armField(regs, present, FF_ARM_REG_PFR0, 20, 0); // PFR0.AdvSIMD[23:20] implemented -> HWCAP_ASIMD
|
||||||
|
|
||||||
// ARMv8.1-A
|
// ARMv8.1-A
|
||||||
bool has_atomics = IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE); // LSE atomics
|
bool has_atomics = armField(regs, present, FF_ARM_REG_ISAR0, 20, 2); // ISAR0.ATOMIC[23:20] >= 2 -> HWCAP_ATOMICS
|
||||||
bool has_crc32 = IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE); // CRC32
|
bool has_crc32 = armField(regs, present, FF_ARM_REG_ISAR0, 16, 1); // ISAR0.CRC32[19:16] >= 1 -> HWCAP_CRC32
|
||||||
|
bool has_asimdrdm = armField(regs, present, FF_ARM_REG_ISAR0, 28, 1); // ISAR0.RDM[31:28] >= 1 -> HWCAP_ASIMDRDM
|
||||||
|
|
||||||
// ARMv8.2-A
|
// ARMv8.2-A
|
||||||
bool has_fp16 = IsProcessorFeaturePresent(PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE); // Half-precision FP
|
bool has_fphp = armField(regs, present, FF_ARM_REG_PFR0, 16, 1); // PFR0.FP[19:16] >= 1 -> HWCAP_FPHP
|
||||||
|
bool has_dcpop = armField(regs, present, FF_ARM_REG_ISAR1, 0, 1); // ISAR1.DPB[3:0] >= 1 -> HWCAP_DCPOP
|
||||||
|
|
||||||
// ARMv8.3-A
|
// ARMv8.3-A
|
||||||
bool has_lrcpc = IsProcessorFeaturePresent(PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE); // LDAPR/LR with RCPC semantics
|
bool has_paca = armField(regs, present, FF_ARM_REG_ISAR1, 4, 1) // ISAR1.APA[7:4] >= 1 -> HWCAP_PACA
|
||||||
bool has_jscvt = IsProcessorFeaturePresent(PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE); // FJCVTZS
|
|| armField(regs, present, FF_ARM_REG_ISAR2, 12, 1); // ISAR2.APA3[15:12] >= 1 (newer encoding)
|
||||||
|
bool has_lrcpc = armField(regs, present, FF_ARM_REG_ISAR1, 20, 1); // ISAR1.LRCPC[23:20] >= 1 -> HWCAP_LRCPC
|
||||||
|
bool has_fcma = armField(regs, present, FF_ARM_REG_ISAR1, 16, 1); // ISAR1.FCMA[19:16] >= 1 -> HWCAP_FCMA
|
||||||
|
bool has_jscvt = armField(regs, present, FF_ARM_REG_ISAR1, 12, 1); // ISAR1.JSCVT[15:12] >= 1 -> HWCAP_JSCVT
|
||||||
|
|
||||||
// ARMv8.4-A
|
// ARMv8.4-A
|
||||||
// My CPU (Apple M1 Pro in VM) does support LSE2, but Windows doesn't detect it for some reason
|
bool has_dit = armField(regs, present, FF_ARM_REG_PFR0, 48, 1); // PFR0.DIT[51:48] >= 1 -> HWCAP_DIT
|
||||||
bool has_lse2 = IsProcessorFeaturePresent(PF_ARM_LSE2_AVAILABLE); // Large System Extensions version 2, optional from v8.2
|
bool has_flagm = armField(regs, present, FF_ARM_REG_ISAR0, 52, 1); // ISAR0.FlagM[55:52] >= 1 -> HWCAP_FLAGM
|
||||||
bool has_dp = IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE); // DotProd, optional from v8.1 (*)
|
bool has_ilrcpc = armField(regs, present, FF_ARM_REG_ISAR1, 20, 2); // ISAR1.LRCPC[23:20] >= 2 -> HWCAP_ILRCPC
|
||||||
|
|
||||||
|
// ARMv8.5-A
|
||||||
|
bool has_bti = armField(regs, present, FF_ARM_REG_PFR1, 0, 1); // PFR1.BT[3:0] >= 1 -> HWCAP2_BTI
|
||||||
|
bool has_sb = armField(regs, present, FF_ARM_REG_ISAR1, 36, 1); // ISAR1.SB[39:36] >= 1 -> HWCAP_SB
|
||||||
|
bool has_dcpodp = armField(regs, present, FF_ARM_REG_ISAR1, 0, 2); // ISAR1.DPB[3:0] >= 2 -> HWCAP2_DCPODP
|
||||||
|
bool has_flagm2 = armField(regs, present, FF_ARM_REG_ISAR0, 52, 2); // ISAR0.FlagM[55:52] >= 2 -> HWCAP2_FLAGM2
|
||||||
|
bool has_frint = armField(regs, present, FF_ARM_REG_ISAR1, 32, 1); // ISAR1.FRINTTS[35:32] >= 1 -> HWCAP2_FRINT
|
||||||
|
|
||||||
// ARMv9.0-A
|
// ARMv9.0-A
|
||||||
bool has_sve2 = IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE); // SVE2
|
bool has_sve2 = armField(regs, present, FF_ARM_REG_ZFR0, 0, 1); // ZFR0.SVEver[3:0] >= 1 (1 = SVE2) -> HWCAP2_SVE2
|
||||||
|
|
||||||
// ARMv9.1-A
|
// ARMv9.1-A
|
||||||
// ARMv8.6-A
|
// ARMv8.6-A
|
||||||
bool has_bf16 = IsProcessorFeaturePresent(PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE); // BF16, optional from v8.2
|
// BF16/I8MM are shared by both version sequences, so the same booleans are
|
||||||
bool has_i8mm = IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE); // Int8 matrix multiply, optional from v8.2
|
// tested in both branches of the chain below.
|
||||||
|
bool has_bf16 = armField(regs, present, FF_ARM_REG_ISAR1, 44, 1); // ISAR1.BF16[47:44] >= 1 -> HWCAP2_BF16
|
||||||
|
bool has_i8mm = armField(regs, present, FF_ARM_REG_ISAR1, 52, 1); // ISAR1.I8MM[55:52] >= 1 -> HWCAP2_I8MM
|
||||||
|
|
||||||
// ARMv8.7-A
|
// ARMv8.7-A
|
||||||
bool has_ebf16 = IsProcessorFeaturePresent(PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE); // Extended BFloat16 behaviors, optional from v8.2
|
bool has_afp = armField(regs, present, FF_ARM_REG_MMFR1, 44, 1); // MMFR1.AFP[47:44] >= 1 -> HWCAP2_AFP
|
||||||
|
|
||||||
// ARMv9.2-A
|
// ARMv9.2-A
|
||||||
bool has_sme = IsProcessorFeaturePresent(PF_ARM_SME_INSTRUCTIONS_AVAILABLE); // SME
|
bool has_sme = armField(regs, present, FF_ARM_REG_PFR1, 24, 1); // PFR1.SME[27:24] >= 1 -> HWCAP2_SME
|
||||||
|
|
||||||
// ARMv9.3-A
|
// ARMv9.3-A
|
||||||
bool has_sme2 = IsProcessorFeaturePresent(PF_ARM_SME2_INSTRUCTIONS_AVAILABLE); // SME2
|
bool has_sme2 = armField(regs, present, FF_ARM_REG_PFR1, 24, 2); // PFR1.SME[27:24] >= 2 -> HWCAP2_SME2
|
||||||
|
|
||||||
|
// ARMv8.8-A
|
||||||
|
bool has_mops = armField(regs, present, FF_ARM_REG_ISAR2, 16, 1); // ISAR2.MOPS[19:16] >= 1 -> HWCAP2_MOPS
|
||||||
|
|
||||||
|
// ARMv8.9-A
|
||||||
|
bool has_cssc = armField(regs, present, FF_ARM_REG_ISAR2, 52, 1); // ISAR2.CSSC[55:52] >= 1 -> HWCAP2_CSSC
|
||||||
|
|
||||||
// ARMv9.4-A
|
// ARMv9.4-A
|
||||||
bool has_sme2p1 = IsProcessorFeaturePresent(PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE); // SME2.1
|
bool has_sme2p1 = armField(regs, present, FF_ARM_REG_SMFR0, 56, 2); // SMFR0.SMEver[59:56] >= 2 -> HWCAP2_SME2P1
|
||||||
|
|
||||||
|
// ARMv9.5-A
|
||||||
|
bool has_f8e4m3 = armBit(regs, present, FF_ARM_REG_FPFR0, 1); // FPFR0.F8E4M3[1] -> HWCAP2_F8E4M3
|
||||||
|
bool has_f8e5m2 = armBit(regs, present, FF_ARM_REG_FPFR0, 0); // FPFR0.F8E5M2[0] -> HWCAP2_F8E5M2
|
||||||
|
|
||||||
|
// ARMv9.6-A
|
||||||
|
bool has_cmpbr = armField(regs, present, FF_ARM_REG_ISAR2, 52, 2); // ISAR2.CSSC[55:52] >= 2 -> HWCAP_CMPBR
|
||||||
|
bool has_fprcvt = armField(regs, present, FF_ARM_REG_ISAR3, 28, 1); // ISAR3.FPRCVT[31:28] >= 1 -> HWCAP_FPRCVT
|
||||||
|
|
||||||
|
// The version chain (both the ARMv9/ARMv8 split and the order of the
|
||||||
|
// checks) must stay identical to the Linux implementation above, otherwise
|
||||||
|
// the same CPU reports a different march depending on the OS.
|
||||||
if (has_sve2 || has_sme) {
|
if (has_sve2 || has_sme) {
|
||||||
// ARMv9 family
|
// ARMv9
|
||||||
if (has_sme2p1) {
|
if (has_cmpbr && has_fprcvt) {
|
||||||
|
cpu->march = "ARMv9.6-A";
|
||||||
|
} else if (has_f8e5m2 && has_f8e4m3) {
|
||||||
|
cpu->march = "ARMv9.5-A";
|
||||||
|
} else if (has_sme2p1) {
|
||||||
cpu->march = "ARMv9.4-A";
|
cpu->march = "ARMv9.4-A";
|
||||||
} else if (has_sme2) {
|
} else if (has_sme2) {
|
||||||
cpu->march = "ARMv9.3-A";
|
cpu->march = "ARMv9.3-A";
|
||||||
@@ -497,20 +662,26 @@ void ffCPUDetectByCpuid(FFCPUResult* cpu) {
|
|||||||
cpu->march = "ARMv9.0-A";
|
cpu->march = "ARMv9.0-A";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ARMv8 family
|
// ARMv8
|
||||||
if (has_ebf16) {
|
if (has_cssc) {
|
||||||
|
cpu->march = "ARMv8.9-A";
|
||||||
|
} else if (has_mops) {
|
||||||
|
cpu->march = "ARMv8.8-A";
|
||||||
|
} else if (has_afp) {
|
||||||
cpu->march = "ARMv8.7-A";
|
cpu->march = "ARMv8.7-A";
|
||||||
} else if (has_i8mm && has_bf16) {
|
} else if (has_i8mm && has_bf16) {
|
||||||
cpu->march = "ARMv8.6-A";
|
cpu->march = "ARMv8.6-A";
|
||||||
} else if (has_dp && has_lse2) {
|
} else if (has_bti && has_sb && has_dcpodp && has_flagm2 && has_frint) {
|
||||||
|
cpu->march = "ARMv8.5-A";
|
||||||
|
} else if (has_dit && has_flagm && has_ilrcpc) {
|
||||||
cpu->march = "ARMv8.4-A";
|
cpu->march = "ARMv8.4-A";
|
||||||
} else if (has_lrcpc && has_jscvt) {
|
} else if (has_paca && has_lrcpc && has_fcma && has_jscvt) {
|
||||||
cpu->march = "ARMv8.3-A";
|
cpu->march = "ARMv8.3-A";
|
||||||
} else if (has_fp16) {
|
} else if (has_fphp && has_dcpop) {
|
||||||
cpu->march = "ARMv8.2-A";
|
cpu->march = "ARMv8.2-A";
|
||||||
} else if (has_atomics && has_crc32) {
|
} else if (has_atomics && has_crc32 && has_asimdrdm) {
|
||||||
cpu->march = "ARMv8.1-A";
|
cpu->march = "ARMv8.1-A";
|
||||||
} else if (has_neon && has_vfp) {
|
} else if (has_asimd && has_fp) {
|
||||||
cpu->march = "ARMv8-A";
|
cpu->march = "ARMv8-A";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,7 +387,8 @@ bool ffdsMutterAvailable(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enrich the displays detected from wl_output with the authoritative data reported by Mutter / Muffin.
|
// Enrich the displays detected from wl_output with the authoritative data reported by Mutter / Muffin.
|
||||||
// Returns true if the display configuration could be retrieved from D-Bus.
|
// Returns true when the D-Bus connection is up and DisplayConfig could be queried. The caller treats
|
||||||
|
// that as authoritative and skips monitors.xml, even if the reply names no primary monitor.
|
||||||
bool ffdsConnectMutter(FFDisplayServerResult* result) {
|
bool ffdsConnectMutter(FFDisplayServerResult* result) {
|
||||||
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
|
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
|
||||||
const char* error = ffDBusLoadData(DBUS_BUS_SESSION, &dbus);
|
const char* error = ffDBusLoadData(DBUS_BUS_SESSION, &dbus);
|
||||||
|
|||||||
@@ -317,16 +317,18 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) {
|
|||||||
data.ffwl_proxy_destroy(registry);
|
data.ffwl_proxy_destroy(registry);
|
||||||
ffwl_display_disconnect(data.display);
|
ffwl_display_disconnect(data.display);
|
||||||
|
|
||||||
bool primaryDetected = false;
|
bool displayConfigRead = false;
|
||||||
#if defined(FF_HAVE_DBUS) && defined(__linux__)
|
#if defined(FF_HAVE_DBUS) && defined(__linux__)
|
||||||
if (data.useMutter) {
|
if (data.useMutter) {
|
||||||
// Mutter / Muffin reports the primary monitor and the raw EDID directly
|
// A working D-Bus connection with a reachable DisplayConfig is authoritative.
|
||||||
primaryDetected = ffdsConnectMutter(result);
|
// Don't second-guess it with monitors.xml even when the reply names no primary
|
||||||
|
// monitor (e.g. connector names that happen to match no wl_output).
|
||||||
|
displayConfigRead = ffdsConnectMutter(result);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Fallback for compositors that do not expose the monitor configuration, or when it could not be read
|
// Fallback for compositors that do not expose the monitor configuration, or when it could not be read
|
||||||
if (!primaryDetected) {
|
if (!displayConfigRead) {
|
||||||
const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell")
|
const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell")
|
||||||
? "monitors.xml"
|
? "monitors.xml"
|
||||||
: ffStrbufEqualS(&result->wmProcessName, "cinnamon")
|
: ffStrbufEqualS(&result->wmProcessName, "cinnamon")
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ typedef struct FFFindGamepadContext {
|
|||||||
// The GameController framework's own devices, and how many of them there were. `set` is the IOKit
|
// The GameController framework's own devices, and how many of them there were. `set` is the IOKit
|
||||||
// side, used only to borrow the serial -- see `findHIDDeviceForController()`.
|
// side, used only to borrow the serial -- see `findHIDDeviceForController()`.
|
||||||
static uint32_t findGameControllerDevices(FFlist* devices, NSSet* set) {
|
static uint32_t findGameControllerDevices(FFlist* devices, NSSet* set) {
|
||||||
|
#ifdef MAC_OS_VERSION_11_0
|
||||||
if (@available(macOS 11.0, *)) {
|
if (@available(macOS 11.0, *)) {
|
||||||
// See the note at the top: the list stays empty until the run loop has been pumped once, and
|
// See the note at the top: the list stays empty until the run loop has been pumped once, and
|
||||||
// the timeout only bounds the case where the framework has nothing to report at all. The
|
// the timeout only bounds the case where the framework has nothing to report at all. The
|
||||||
@@ -222,6 +223,8 @@ static uint32_t findGameControllerDevices(FFlist* devices, NSSet* set) {
|
|||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +237,7 @@ static uint32_t findGameControllerDevices(FFlist* devices, NSSet* set) {
|
|||||||
// Apple's whitelist, fails this and skips the wait entirely instead of sitting through a timeout for
|
// Apple's whitelist, fails this and skips the wait entirely instead of sitting through a timeout for
|
||||||
// a list that will never contain it.
|
// a list that will never contain it.
|
||||||
static bool anyDeviceIsClaimedByGameController(NSSet* set) {
|
static bool anyDeviceIsClaimedByGameController(NSSet* set) {
|
||||||
|
#ifdef MAC_OS_VERSION_11_0
|
||||||
if (@available(macOS 11.0, *)) {
|
if (@available(macOS 11.0, *)) {
|
||||||
for (id value in set) {
|
for (id value in set) {
|
||||||
if ([GCController supportsHIDDevice: (__bridge IOHIDDeviceRef) value]) {
|
if ([GCController supportsHIDDevice: (__bridge IOHIDDeviceRef) value]) {
|
||||||
@@ -241,12 +245,15 @@ static bool anyDeviceIsClaimedByGameController(NSSet* set) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void enumSet(IOHIDDeviceRef value, FFFindGamepadContext* ctx) {
|
static void enumSet(IOHIDDeviceRef value, FFFindGamepadContext* ctx) {
|
||||||
// A device the framework owns is already in the list under its own name; see the note at the
|
// A device the framework owns is already in the list under its own name; see the note at the
|
||||||
// top for why this is conditional on the framework having contributed anything at all.
|
// top for why this is conditional on the framework having contributed anything at all.
|
||||||
|
#ifdef MAC_OS_VERSION_11_0
|
||||||
if (ctx->gameControllerCount > 0) {
|
if (ctx->gameControllerCount > 0) {
|
||||||
if (@available(macOS 11.0, *)) {
|
if (@available(macOS 11.0, *)) {
|
||||||
if ([GCController supportsHIDDevice:value]) {
|
if ([GCController supportsHIDDevice:value]) {
|
||||||
@@ -254,6 +261,7 @@ static void enumSet(IOHIDDeviceRef value, FFFindGamepadContext* ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *ctx->devices);
|
FFGamepadDevice* device = FF_LIST_ADD(FFGamepadDevice, *ctx->devices);
|
||||||
ffStrbufInit(&device->serial);
|
ffStrbufInit(&device->serial);
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ static pid_t getShellInfo(FFShellResult* result, pid_t pid) {
|
|||||||
ffStrbufEqualS(&result->processName, "valgrind") ||
|
ffStrbufEqualS(&result->processName, "valgrind") ||
|
||||||
ffStrbufEqualS(&result->processName, "fastfetch") || // #994
|
ffStrbufEqualS(&result->processName, "fastfetch") || // #994
|
||||||
ffStrbufEqualS(&result->processName, "flashfetch") ||
|
ffStrbufEqualS(&result->processName, "flashfetch") ||
|
||||||
|
ffStrbufEqualS(&result->processName, "mise") ||
|
||||||
|
ffStrbufEqualS(&result->processName, "mise-shim") ||
|
||||||
|
ffStrbufEqualS(&result->processName, "atuin") ||
|
||||||
ffStrbufEqualS(&result->processName, "proot") ||
|
ffStrbufEqualS(&result->processName, "proot") ||
|
||||||
ffStrbufEqualS(&result->processName, "script") ||
|
ffStrbufEqualS(&result->processName, "script") ||
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ static uint32_t getShellInfo(FFShellResult* result, uint32_t pid) {
|
|||||||
if (
|
if (
|
||||||
!gui && (ffStrbufIgnCaseEqualS(&result->prettyName, "sudo") || ffStrbufIgnCaseEqualS(&result->prettyName, "su") || ffStrbufIgnCaseEqualS(&result->prettyName, "gdb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb-dap") || ffStrbufIgnCaseEqualS(&result->prettyName, "python") || // python on windows generates shim executables
|
!gui && (ffStrbufIgnCaseEqualS(&result->prettyName, "sudo") || ffStrbufIgnCaseEqualS(&result->prettyName, "su") || ffStrbufIgnCaseEqualS(&result->prettyName, "gdb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb-dap") || ffStrbufIgnCaseEqualS(&result->prettyName, "python") || // python on windows generates shim executables
|
||||||
ffStrbufIgnCaseEqualS(&result->prettyName, "fastfetch") || // scoop warps the real binaries with a "shim" exe
|
ffStrbufIgnCaseEqualS(&result->prettyName, "fastfetch") || // scoop warps the real binaries with a "shim" exe
|
||||||
ffStrbufIgnCaseEqualS(&result->prettyName, "flashfetch") || ffStrbufContainIgnCaseS(&result->prettyName, "debug") || ffStrbufContainIgnCaseS(&result->prettyName, "time") || ffStrbufStartsWithIgnCaseS(&result->prettyName, "ConEmuC") // https://github.com/fastfetch-cli/fastfetch/issues/488#issuecomment-1619982014
|
ffStrbufIgnCaseEqualS(&result->prettyName, "flashfetch") || ffStrbufIgnCaseEqualS(&result->prettyName, "mise") || ffStrbufIgnCaseEqualS(&result->prettyName, "mise-shim") || ffStrbufContainIgnCaseS(&result->prettyName, "debug") || ffStrbufContainIgnCaseS(&result->prettyName, "time") || ffStrbufStartsWithIgnCaseS(&result->prettyName, "ConEmuC") // https://github.com/fastfetch-cli/fastfetch/issues/488#issuecomment-1619982014
|
||||||
)) {
|
)) {
|
||||||
ffStrbufClear(&result->processName);
|
ffStrbufClear(&result->processName);
|
||||||
ffStrbufClear(&result->prettyName);
|
ffStrbufClear(&result->prettyName);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "common/ffdata.h"
|
#include "common/ffdata.h"
|
||||||
#include "detection/version/version.h"
|
#include "detection/version/version.h"
|
||||||
#include "logo/logo.h"
|
#include "logo/logo.h"
|
||||||
|
#include "logo/spin/spin.h"
|
||||||
#include "common/FFcache.h"
|
#include "common/FFcache.h"
|
||||||
#include "common/commandoption.h"
|
#include "common/commandoption.h"
|
||||||
#include "common/genconfig.h"
|
#include "common/genconfig.h"
|
||||||
@@ -754,6 +755,12 @@ static void parseConfigFiles(FFdata* data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `-30`, `-.5`: a negative number is the value of the previous option, not the
|
||||||
|
// name of the next one (`--logo-spin-tilt -30`).
|
||||||
|
static bool isNegativeNumber(const char* str) {
|
||||||
|
return str[0] == '-' && (isdigit((unsigned char) str[1]) || (str[1] == '.' && isdigit((unsigned char) str[2])));
|
||||||
|
}
|
||||||
|
|
||||||
static void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(FFdata* data, char* key, char* value)) {
|
static void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(FFdata* data, char* key, char* value)) {
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
const char* key = argv[i];
|
const char* key = argv[i];
|
||||||
@@ -768,6 +775,7 @@ static void parseArguments(FFdata* data, int argc, char** argv, void (*parser)(F
|
|||||||
|
|
||||||
if (i == argc - 1 || (argv[i + 1][0] == '-' && argv[i + 1][1] != '\0' && // `-` is used as an alias for `/dev/stdin`
|
if (i == argc - 1 || (argv[i + 1][0] == '-' && argv[i + 1][1] != '\0' && // `-` is used as an alias for `/dev/stdin`
|
||||||
!ffStrEqualsIgnCase(argv[i], "--separator-string") // Separator string can start with a -
|
!ffStrEqualsIgnCase(argv[i], "--separator-string") // Separator string can start with a -
|
||||||
|
&& !isNegativeNumber(argv[i + 1]) // A negative number is a value
|
||||||
)) {
|
)) {
|
||||||
parser(data, argv[i], nullptr);
|
parser(data, argv[i], nullptr);
|
||||||
} else {
|
} else {
|
||||||
@@ -817,6 +825,12 @@ static void run(FFdata* data) {
|
|||||||
fputs("\e[H", stdout); // Move cursor to the top left corner to overwrite the previous output
|
fputs("\e[H", stdout); // Move cursor to the top left corner to overwrite the previous output
|
||||||
instance.state.keysHeight = 0; // Reset keysHeight so `ffLogoPrintRemaining` will recalculate it
|
instance.state.keysHeight = 0; // Reset keysHeight so `ffLogoPrintRemaining` will recalculate it
|
||||||
ffCacheInvalidateAll(); // Drop the detection results cached for the previous round, so that this round re-detects them
|
ffCacheInvalidateAll(); // Drop the detection results cached for the previous round, so that this round re-detects them
|
||||||
|
// `--logo-type spin`: one rotation step per refresh, so the logo turns in sync with
|
||||||
|
// the modules instead of owning a timer of its own. A still logo, or any other logo
|
||||||
|
// type, costs a single `false` here.
|
||||||
|
if (ffSpinTick()) {
|
||||||
|
ffLogoPrint();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,17 @@ typedef struct FFLogoLineCacheState {
|
|||||||
uint32_t rightOffset;
|
uint32_t rightOffset;
|
||||||
} FFLogoLineCacheState;
|
} FFLogoLineCacheState;
|
||||||
|
|
||||||
|
// `--logo-type spin`: kept here instead of in a file-static block so that the
|
||||||
|
// state lives with the rest of the run and starts clean on every invocation.
|
||||||
|
typedef struct FFLogoSpinState {
|
||||||
|
float degrees; // rotation of the frame drawn last
|
||||||
|
double lastDrawMs; // when that frame was drawn
|
||||||
|
bool started; // `logo.spin.angle` has been applied
|
||||||
|
bool printed; // a frame was drawn, so `ffSpinTick` has work to do
|
||||||
|
uint32_t printedWidth;
|
||||||
|
uint32_t printedHeight;
|
||||||
|
} FFLogoSpinState;
|
||||||
|
|
||||||
typedef struct FFstate {
|
typedef struct FFstate {
|
||||||
uint32_t logoWidth;
|
uint32_t logoWidth;
|
||||||
uint32_t logoHeight;
|
uint32_t logoHeight;
|
||||||
@@ -35,6 +46,7 @@ typedef struct FFstate {
|
|||||||
uint32_t dynamicInterval;
|
uint32_t dynamicInterval;
|
||||||
FFPlatform platform;
|
FFPlatform platform;
|
||||||
FFLogoLineCacheState logoLineCache;
|
FFLogoLineCacheState logoLineCache;
|
||||||
|
FFLogoSpinState logoSpin;
|
||||||
} FFstate;
|
} FFstate;
|
||||||
|
|
||||||
typedef struct FFinstance {
|
typedef struct FFinstance {
|
||||||
|
|||||||
@@ -223,6 +223,18 @@ static const FFlogo O[] = {
|
|||||||
.colorTitle = FF_COLOR_FG_GREEN,
|
.colorTitle = FF_COLOR_FG_GREEN,
|
||||||
},
|
},
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_MICRO
|
||||||
|
// OpenSuseLeapMicro
|
||||||
|
{
|
||||||
|
.names = { "opensuse-leap-micro" },
|
||||||
|
.lines = FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_MICRO,
|
||||||
|
.colors = {
|
||||||
|
FF_COLOR_FG_GREEN,
|
||||||
|
},
|
||||||
|
.colorKeys = FF_COLOR_FG_GREEN,
|
||||||
|
.colorTitle = FF_COLOR_FG_GREEN,
|
||||||
|
},
|
||||||
|
#endif
|
||||||
#ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_OLD
|
#ifdef FASTFETCH_DATATEXT_LOGO_OPENSUSE_LEAP_OLD
|
||||||
// OpenSuseLeapOld
|
// OpenSuseLeapOld
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
+=+
|
||||||
|
+=====+
|
||||||
|
+==== ====+
|
||||||
|
+==== ====+
|
||||||
|
+==== ====+
|
||||||
|
+==== ======= ====+
|
||||||
|
+==== +===+++===+ ====+
|
||||||
|
========== === === ==========
|
||||||
|
+++++++==== ===+ +=== ====+++++++
|
||||||
|
+==== +=======+ ====+
|
||||||
|
== +==== +++ ====+ ==
|
||||||
|
==== +==== ====+ ====
|
||||||
|
==== +==== ====+ ====
|
||||||
|
==== +=======+ ====
|
||||||
|
==== +===+ ====
|
||||||
|
==== ====
|
||||||
|
====+====
|
||||||
|
=====
|
||||||
|
=
|
||||||
+12
-6
@@ -8,6 +8,7 @@
|
|||||||
#include "detection/os/os.h"
|
#include "detection/os/os.h"
|
||||||
#include "detection/terminalshell/terminalshell.h"
|
#include "detection/terminalshell/terminalshell.h"
|
||||||
#include "detection/terminalsize/terminalsize.h"
|
#include "detection/terminalsize/terminalsize.h"
|
||||||
|
#include "logo/spin/spin.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -324,7 +325,7 @@ void ffLogoPrintChars(const char* data, bool doColorReplacement) {
|
|||||||
logoLineCacheClear(cache);
|
logoLineCacheClear(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logoApplyColors(const FFlogo* logo, bool replacement) {
|
void ffLogoApplyColors(const FFlogo* logo, bool replacement) {
|
||||||
if (instance.config.display.colorTitle.length == 0) {
|
if (instance.config.display.colorTitle.length == 0) {
|
||||||
ffStrbufSetStatic(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]);
|
ffStrbufSetStatic(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]);
|
||||||
}
|
}
|
||||||
@@ -439,14 +440,14 @@ static const FFlogo* logoGetBuiltinDetected(FFLogoSize size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void logoPrintStruct(const FFlogo* logo) {
|
static void logoPrintStruct(const FFlogo* logo) {
|
||||||
logoApplyColors(logo, true);
|
ffLogoApplyColors(logo, true);
|
||||||
|
|
||||||
ffLogoPrintChars(logo->lines, true);
|
ffLogoPrintChars(logo->lines, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void logoPrintNone(void) {
|
static void logoPrintNone(void) {
|
||||||
if (!instance.config.display.pipe) {
|
if (!instance.config.display.pipe) {
|
||||||
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
|
ffLogoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
|
||||||
}
|
}
|
||||||
instance.state.logoHeight = 0;
|
instance.state.logoHeight = 0;
|
||||||
instance.state.logoWidth = 0;
|
instance.state.logoWidth = 0;
|
||||||
@@ -484,7 +485,7 @@ static bool logoPrintData(bool doColorReplacement, FFstrbuf* source) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
|
ffLogoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
|
||||||
ffLogoPrintChars(source->chars, doColorReplacement);
|
ffLogoPrintChars(source->chars, doColorReplacement);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -548,7 +549,7 @@ static bool logoPrintFileIfExists(bool doColorReplacement, bool raw) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
|
ffLogoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
|
||||||
if (raw) {
|
if (raw) {
|
||||||
return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors);
|
return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors);
|
||||||
}
|
}
|
||||||
@@ -563,7 +564,7 @@ static bool logoPrintImageIfExists(FFLogoType logo, bool printError) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
|
ffLogoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -654,6 +655,11 @@ void ffLogoPrint(void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options->type == FF_LOGO_TYPE_SPIN) {
|
||||||
|
ffSpinLogoPrint();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the source is not set, we can directly print the detected logo.
|
// If the source is not set, we can directly print the detected logo.
|
||||||
if (options->source.length == 0) {
|
if (options->source.length == 0) {
|
||||||
ffLogoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL);
|
ffLogoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL);
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ void ffLogoBuiltinListAutocompletion(void);
|
|||||||
void ffLogoPrintDetected(FFLogoSize size);
|
void ffLogoPrintDetected(FFLogoSize size);
|
||||||
const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size);
|
const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size);
|
||||||
const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size);
|
const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size);
|
||||||
|
// Fills the `logo.color.N` slots that the user did not set from `logo`.
|
||||||
|
void ffLogoApplyColors(const FFlogo* logo, bool replacement);
|
||||||
|
|
||||||
// builtin.c
|
// builtin.c
|
||||||
extern const FFlogo* ffLogoBuiltins[];
|
extern const FFlogo* ffLogoBuiltins[];
|
||||||
|
|||||||
@@ -0,0 +1,506 @@
|
|||||||
|
#include "logo/spin/render.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// Terminal cells are about twice as tall as wide. World units are cell
|
||||||
|
// heights, so one column is half a unit wide.
|
||||||
|
#define FF_SPIN_CELL_ASPECT 0.5f
|
||||||
|
// Camera distance in bounding-sphere radii. Smaller means stronger perspective.
|
||||||
|
#define FF_SPIN_CAMERA_RADII 4.0f
|
||||||
|
|
||||||
|
// Shading for the slab's edges, dark to bright. The faces keep the logo's glyphs.
|
||||||
|
static const uint32_t FFSpinWallRamp[] = { 0x2591, 0x2592, 0x2593, 0x2588 }; // ░▒▓█
|
||||||
|
static const float FFSpinLight[3] = { -0.48f, 0.6f, 0.64f }; // unit length, camera space
|
||||||
|
|
||||||
|
typedef struct SpinGeometry {
|
||||||
|
float hx, hy, hz; // half extents of the slab
|
||||||
|
float camera; // camera distance from the slab center
|
||||||
|
float focal; // maps the front face 1:1 onto cells at yaw 0
|
||||||
|
} SpinGeometry;
|
||||||
|
|
||||||
|
static SpinGeometry spinGeometry(const FFSpinArt* art) {
|
||||||
|
SpinGeometry g;
|
||||||
|
g.hx = (float) art->width * FF_SPIN_CELL_ASPECT * 0.5f;
|
||||||
|
g.hy = (float) art->height * 0.5f;
|
||||||
|
g.hz = fmaxf(0.3f, fmaxf(g.hx, g.hy) * 0.055f);
|
||||||
|
float radius = sqrtf(g.hx * g.hx + g.hy * g.hy + g.hz * g.hz);
|
||||||
|
g.camera = radius * FF_SPIN_CAMERA_RADII;
|
||||||
|
g.focal = g.camera - g.hz;
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t utf8Decode(const char** cursor) {
|
||||||
|
const unsigned char* s = (const unsigned char*) *cursor;
|
||||||
|
uint32_t length, glyph;
|
||||||
|
if (s[0] < 0x80) {
|
||||||
|
*cursor += 1;
|
||||||
|
return s[0];
|
||||||
|
}
|
||||||
|
if ((s[0] & 0xE0) == 0xC0) {
|
||||||
|
length = 2;
|
||||||
|
glyph = s[0] & 0x1F;
|
||||||
|
} else if ((s[0] & 0xF0) == 0xE0) {
|
||||||
|
length = 3;
|
||||||
|
glyph = s[0] & 0x0F;
|
||||||
|
} else if ((s[0] & 0xF8) == 0xF0) {
|
||||||
|
length = 4;
|
||||||
|
glyph = s[0] & 0x07;
|
||||||
|
} else {
|
||||||
|
*cursor += 1;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
for (uint32_t i = 1; i < length; ++i) {
|
||||||
|
if ((s[i] & 0xC0) != 0x80) {
|
||||||
|
*cursor += i;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
glyph = (glyph << 6) | (s[i] & 0x3F);
|
||||||
|
}
|
||||||
|
*cursor += length;
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool glyphIsBlank(uint32_t glyph) {
|
||||||
|
return glyph <= ' ' || glyph == 0x7F || glyph == 0xA0 || glyph == 0x2800;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t paletteAdd(FFSpinArt* art, const char* params, uint32_t length) {
|
||||||
|
if (length == 0 || (length == 1 && params[0] == '0')) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (uint32_t i = FF_SPIN_PALETTE_LOGO_SLOTS; i < art->paletteCount; ++i) {
|
||||||
|
if (art->palette[i].length == length && memcmp(art->palette[i].chars, params, length) == 0) {
|
||||||
|
return (uint8_t) i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (art->paletteCount >= FF_SPIN_PALETTE_MAX) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ffStrbufSetNS(&art->palette[art->paletteCount], length, params);
|
||||||
|
return (uint8_t) art->paletteCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walks fastfetch logo text ($1..$9 colors, `$$`, tabs, SGR escapes). With
|
||||||
|
// `fill` false it only measures the grid.
|
||||||
|
static void artWalk(FFSpinArt* art, const char* text, bool fill, uint32_t* outWidth, uint32_t* outHeight) {
|
||||||
|
uint32_t row = 0, col = 0, width = 0;
|
||||||
|
uint8_t color = 1; // fastfetch starts every logo in $1
|
||||||
|
const char* p = text;
|
||||||
|
while (*p) {
|
||||||
|
if (*p == '\r') {
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*p == '\n') {
|
||||||
|
if (col > width) width = col;
|
||||||
|
++row;
|
||||||
|
col = 0;
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*p == '\t') {
|
||||||
|
col += 4;
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*p == '\e') {
|
||||||
|
if (p[1] != '[') {
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char* params = p + 2;
|
||||||
|
const char* end = params;
|
||||||
|
while ((*end >= '0' && *end <= '9') || *end == ';') {
|
||||||
|
++end;
|
||||||
|
}
|
||||||
|
if (*end == 'm' && fill) {
|
||||||
|
color = paletteAdd(art, params, (uint32_t) (end - params));
|
||||||
|
}
|
||||||
|
p = *end ? end + 1 : end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*p == '$') {
|
||||||
|
if (p[1] >= '1' && p[1] <= '9') {
|
||||||
|
color = (uint8_t) (p[1] - '0');
|
||||||
|
p += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (p[1] == '$') {
|
||||||
|
++p; // `$$` is a literal dollar sign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint32_t glyph = utf8Decode(&p);
|
||||||
|
if (fill && !glyphIsBlank(glyph)) {
|
||||||
|
size_t index = (size_t) row * art->width + col;
|
||||||
|
art->glyphs[index] = glyph;
|
||||||
|
art->colors[index] = color;
|
||||||
|
}
|
||||||
|
++col;
|
||||||
|
}
|
||||||
|
if (col > width) width = col;
|
||||||
|
if (col > 0) ++row;
|
||||||
|
*outWidth = width;
|
||||||
|
*outHeight = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drops blank rows and columns around the art, so it spins around its center.
|
||||||
|
static bool artCrop(FFSpinArt* art) {
|
||||||
|
uint32_t minX = UINT32_MAX, minY = UINT32_MAX, maxX = 0, maxY = 0;
|
||||||
|
for (uint32_t y = 0; y < art->height; ++y) {
|
||||||
|
for (uint32_t x = 0; x < art->width; ++x) {
|
||||||
|
if (art->glyphs[(size_t) y * art->width + x] == 0) continue;
|
||||||
|
if (x < minX) minX = x;
|
||||||
|
if (x > maxX) maxX = x;
|
||||||
|
if (y < minY) minY = y;
|
||||||
|
if (y > maxY) maxY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (minX == UINT32_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint32_t width = maxX - minX + 1, height = maxY - minY + 1;
|
||||||
|
if (width == art->width && height == art->height) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uint32_t* glyphs = (uint32_t*) malloc((size_t) width * height * sizeof(uint32_t));
|
||||||
|
uint8_t* colors = (uint8_t*) malloc((size_t) width * height);
|
||||||
|
if (!glyphs || !colors) {
|
||||||
|
free(glyphs);
|
||||||
|
free(colors);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (uint32_t y = 0; y < height; ++y) {
|
||||||
|
size_t from = (size_t) (minY + y) * art->width + minX;
|
||||||
|
memcpy(glyphs + (size_t) y * width, art->glyphs + from, width * sizeof(uint32_t));
|
||||||
|
memcpy(colors + (size_t) y * width, art->colors + from, width);
|
||||||
|
}
|
||||||
|
free(art->glyphs);
|
||||||
|
free(art->colors);
|
||||||
|
art->glyphs = glyphs;
|
||||||
|
art->colors = colors;
|
||||||
|
art->width = width;
|
||||||
|
art->height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ffSpinArtParse(FFSpinArt* art, const char* text, const FFstrbuf* colors, uint32_t colorCount) {
|
||||||
|
memset(art, 0, sizeof(*art));
|
||||||
|
art->paletteCount = FF_SPIN_PALETTE_LOGO_SLOTS;
|
||||||
|
for (uint32_t i = 0; colors && i < colorCount && i + 1 < FF_SPIN_PALETTE_LOGO_SLOTS; ++i) {
|
||||||
|
ffStrbufSet(&art->palette[i + 1], &colors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t width, height;
|
||||||
|
artWalk(art, text, false, &width, &height);
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
art->glyphs = (uint32_t*) calloc((size_t) width * height, sizeof(uint32_t));
|
||||||
|
art->colors = (uint8_t*) calloc((size_t) width * height, 1);
|
||||||
|
if (!art->glyphs || !art->colors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
art->width = width;
|
||||||
|
art->height = height;
|
||||||
|
artWalk(art, text, true, &width, &height);
|
||||||
|
return artCrop(art);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinArtDestroy(FFSpinArt* art) {
|
||||||
|
free(art->glyphs);
|
||||||
|
free(art->colors);
|
||||||
|
for (uint32_t i = 0; i < FF_SPIN_PALETTE_MAX; ++i) {
|
||||||
|
ffStrbufDestroy(&art->palette[i]);
|
||||||
|
}
|
||||||
|
memset(art, 0, sizeof(*art));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ffSpinFrameInit(FFSpinFrame* frame, uint32_t width, uint32_t height) {
|
||||||
|
memset(frame, 0, sizeof(*frame));
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
frame->glyphs = (uint32_t*) calloc((size_t) width * height, sizeof(uint32_t));
|
||||||
|
frame->colors = (uint8_t*) calloc((size_t) width * height, 1);
|
||||||
|
if (!frame->glyphs || !frame->colors) {
|
||||||
|
ffSpinFrameDestroy(frame);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
frame->width = width;
|
||||||
|
frame->height = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinFrameDestroy(FFSpinFrame* frame) {
|
||||||
|
free(frame->glyphs);
|
||||||
|
free(frame->colors);
|
||||||
|
memset(frame, 0, sizeof(*frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void frameClear(FFSpinFrame* frame) {
|
||||||
|
memset(frame->glyphs, 0, (size_t) frame->width * frame->height * sizeof(uint32_t));
|
||||||
|
memset(frame->colors, 0, (size_t) frame->width * frame->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinRotationMatrix(float yaw, float tilt, float out[3][3], float outInverse[3][3]) {
|
||||||
|
float cy = cosf(yaw), sy = sinf(yaw);
|
||||||
|
float cx = cosf(tilt), sx = sinf(tilt);
|
||||||
|
// Rx(tilt) * Ry(yaw): spin around the logo's vertical axis, then lean it.
|
||||||
|
const float m[3][3] = {
|
||||||
|
{ cy, 0, sy },
|
||||||
|
{ sx * sy, cx, -sx * cy },
|
||||||
|
{ -cx * sy, sx, cx * cy },
|
||||||
|
};
|
||||||
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
|
for (uint32_t j = 0; j < 3; ++j) {
|
||||||
|
out[i][j] = m[i][j];
|
||||||
|
outInverse[j][i] = m[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ffSpinRotationIsOrthonormal(const float m[3][3], float epsilon) {
|
||||||
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
|
for (uint32_t j = 0; j < 3; ++j) {
|
||||||
|
float dot = m[0][i] * m[0][j] + m[1][i] * m[1][j] + m[2][i] * m[2][j];
|
||||||
|
if (fabsf(dot - (i == j ? 1.0f : 0.0f)) > epsilon) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
|
||||||
|
- m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
|
||||||
|
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
|
||||||
|
return fabsf(det - 1.0f) <= epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ffSpinMirrorGlyph(uint32_t glyph) {
|
||||||
|
static const uint32_t pairs[][2] = {
|
||||||
|
{ '/', '\\' }, { '(', ')' }, { '[', ']' }, { '{', '}' }, { '<', '>' },
|
||||||
|
{ 'b', 'd' }, { 'p', 'q' },
|
||||||
|
{ 0x258C, 0x2590 }, { 0x2598, 0x259D }, { 0x2596, 0x2597 }, { 0x259B, 0x259C },
|
||||||
|
{ 0x2599, 0x259F }, { 0x259A, 0x259E }, { 0x25E2, 0x25E3 }, { 0x25E4, 0x25E5 },
|
||||||
|
{ 0x2571, 0x2572 }, { 0x250C, 0x2510 }, { 0x2514, 0x2518 }, { 0x251C, 0x2524 },
|
||||||
|
{ 0x256D, 0x256E }, { 0x2570, 0x256F }, { 0x2554, 0x2557 }, { 0x255A, 0x255D },
|
||||||
|
};
|
||||||
|
for (uint32_t i = 0; i < sizeof(pairs) / sizeof(pairs[0]); ++i) {
|
||||||
|
if (glyph == pairs[i][0]) return pairs[i][1];
|
||||||
|
if (glyph == pairs[i][1]) return pairs[i][0];
|
||||||
|
}
|
||||||
|
if (glyph >= 0x2800 && glyph <= 0x28FF) {
|
||||||
|
// Braille: swap the left dot column (1,2,3,7) with the right one (4,5,6,8).
|
||||||
|
uint32_t dots = glyph - 0x2800;
|
||||||
|
return 0x2800 + (((dots & 0x07) << 3) | ((dots & 0x38) >> 3) | ((dots & 0x40) << 1) | ((dots & 0x80) >> 1));
|
||||||
|
}
|
||||||
|
return glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinFrameSize(const FFSpinArt* art, float tilt, uint32_t* width, uint32_t* height) {
|
||||||
|
SpinGeometry g = spinGeometry(art);
|
||||||
|
float maxX = (float) art->width * 0.5f, maxY = (float) art->height * 0.5f;
|
||||||
|
for (uint32_t step = 0; step < 96; ++step) {
|
||||||
|
float rot[3][3], inv[3][3];
|
||||||
|
ffSpinRotationMatrix((float) step * 2.0f * (float) M_PI / 96.0f, tilt, rot, inv);
|
||||||
|
for (uint32_t corner = 0; corner < 8; ++corner) {
|
||||||
|
float p[3] = { corner & 1 ? g.hx : -g.hx, corner & 2 ? g.hy : -g.hy, corner & 4 ? g.hz : -g.hz };
|
||||||
|
float c[3];
|
||||||
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
|
c[i] = rot[i][0] * p[0] + rot[i][1] * p[1] + rot[i][2] * p[2];
|
||||||
|
}
|
||||||
|
float scale = g.focal / (g.camera - c[2]);
|
||||||
|
maxX = fmaxf(maxX, fabsf(c[0]) * scale / FF_SPIN_CELL_ASPECT);
|
||||||
|
maxY = fmaxf(maxY, fabsf(c[1]) * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A cell is drawn when the ray through its center hits, and the outermost
|
||||||
|
// cell centers sit half a cell inside the frame edge.
|
||||||
|
float marginX = ceilf(maxX - (float) art->width * 0.5f - 0.5f);
|
||||||
|
float marginY = ceilf(maxY - (float) art->height * 0.5f - 0.5f);
|
||||||
|
*width = art->width + 2 * (marginX > 0 ? (uint32_t) marginX : 0);
|
||||||
|
*height = art->height + 2 * (marginY > 0 ? (uint32_t) marginY : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinFramePutArt(FFSpinFrame* frame, const FFSpinArt* art) {
|
||||||
|
frameClear(frame);
|
||||||
|
if (frame->width < art->width || frame->height < art->height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t left = (frame->width - art->width) / 2;
|
||||||
|
uint32_t top = (frame->height - art->height) / 2;
|
||||||
|
for (uint32_t y = 0; y < art->height; ++y) {
|
||||||
|
size_t to = (size_t) (top + y) * frame->width + left;
|
||||||
|
size_t from = (size_t) y * art->width;
|
||||||
|
memcpy(frame->glyphs + to, art->glyphs + from, art->width * sizeof(uint32_t));
|
||||||
|
memcpy(frame->colors + to, art->colors + from, art->width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum SpinHit {
|
||||||
|
SPIN_HIT_NONE,
|
||||||
|
SPIN_HIT_FRONT,
|
||||||
|
SPIN_HIT_BACK,
|
||||||
|
SPIN_HIT_WALL,
|
||||||
|
} SpinHit;
|
||||||
|
|
||||||
|
// Casts one ray (object space) through the extruded art. On a hit, `cell` is
|
||||||
|
// the art cell and `normal` the object-space normal of the surface.
|
||||||
|
static SpinHit castRay(const FFSpinArt* art, const SpinGeometry* g, const float o[3], const float d[3], uint32_t* cell, float normal[3]) {
|
||||||
|
const float lo[3] = { -g->hx, -g->hy, -g->hz };
|
||||||
|
const float hi[3] = { g->hx, g->hy, g->hz };
|
||||||
|
float tNear = 0.0f, tFar = INFINITY;
|
||||||
|
int axis = -1;
|
||||||
|
for (int a = 0; a < 3; ++a) {
|
||||||
|
if (fabsf(d[a]) < 1e-12f) {
|
||||||
|
if (o[a] < lo[a] || o[a] > hi[a]) return SPIN_HIT_NONE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float t1 = (lo[a] - o[a]) / d[a];
|
||||||
|
float t2 = (hi[a] - o[a]) / d[a];
|
||||||
|
if (t1 > t2) {
|
||||||
|
float t = t1;
|
||||||
|
t1 = t2;
|
||||||
|
t2 = t;
|
||||||
|
}
|
||||||
|
if (t1 > tNear) {
|
||||||
|
tNear = t1;
|
||||||
|
axis = a;
|
||||||
|
}
|
||||||
|
if (t2 < tFar) tFar = t2;
|
||||||
|
if (tNear > tFar) return SPIN_HIT_NONE;
|
||||||
|
}
|
||||||
|
if (axis < 0) return SPIN_HIT_NONE;
|
||||||
|
|
||||||
|
// Grid coordinates: x in columns from the left, y in rows from the top.
|
||||||
|
const int32_t w = (int32_t) art->width, h = (int32_t) art->height;
|
||||||
|
float gx = (o[0] + d[0] * tNear + g->hx) / FF_SPIN_CELL_ASPECT;
|
||||||
|
float gy = g->hy - (o[1] + d[1] * tNear);
|
||||||
|
float dgx = d[0] / FF_SPIN_CELL_ASPECT;
|
||||||
|
float dgy = -d[1];
|
||||||
|
int32_t cx = (int32_t) floorf(gx), cy = (int32_t) floorf(gy);
|
||||||
|
if (cx < 0) cx = 0;
|
||||||
|
if (cx >= w) cx = w - 1;
|
||||||
|
if (cy < 0) cy = 0;
|
||||||
|
if (cy >= h) cy = h - 1;
|
||||||
|
|
||||||
|
normal[0] = normal[1] = normal[2] = 0.0f;
|
||||||
|
if (art->glyphs[(size_t) cy * art->width + (uint32_t) cx]) {
|
||||||
|
*cell = (uint32_t) cy * art->width + (uint32_t) cx;
|
||||||
|
normal[axis] = d[axis] > 0 ? -1.0f : 1.0f;
|
||||||
|
if (axis == 2) return d[2] < 0 ? SPIN_HIT_FRONT : SPIN_HIT_BACK;
|
||||||
|
return SPIN_HIT_WALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the grid cell by cell (Amanatides-Woo) until the ray leaves the slab.
|
||||||
|
int32_t stepX = dgx > 0 ? 1 : -1, stepY = dgy > 0 ? 1 : -1;
|
||||||
|
float tMaxX = dgx != 0 ? tNear + ((float) (dgx > 0 ? cx + 1 : cx) - gx) / dgx : INFINITY;
|
||||||
|
float tMaxY = dgy != 0 ? tNear + ((float) (dgy > 0 ? cy + 1 : cy) - gy) / dgy : INFINITY;
|
||||||
|
float tDeltaX = dgx != 0 ? fabsf(1.0f / dgx) : INFINITY;
|
||||||
|
float tDeltaY = dgy != 0 ? fabsf(1.0f / dgy) : INFINITY;
|
||||||
|
while (true) {
|
||||||
|
bool alongX = tMaxX < tMaxY;
|
||||||
|
if ((alongX ? tMaxX : tMaxY) > tFar) return SPIN_HIT_NONE;
|
||||||
|
if (alongX) {
|
||||||
|
cx += stepX;
|
||||||
|
tMaxX += tDeltaX;
|
||||||
|
if (cx < 0 || cx >= w) return SPIN_HIT_NONE;
|
||||||
|
} else {
|
||||||
|
cy += stepY;
|
||||||
|
tMaxY += tDeltaY;
|
||||||
|
if (cy < 0 || cy >= h) return SPIN_HIT_NONE;
|
||||||
|
}
|
||||||
|
if (art->glyphs[(size_t) cy * art->width + (uint32_t) cx]) {
|
||||||
|
*cell = (uint32_t) cy * art->width + (uint32_t) cx;
|
||||||
|
if (alongX) {
|
||||||
|
normal[0] = (float) -stepX;
|
||||||
|
} else {
|
||||||
|
normal[1] = (float) stepY; // grid rows grow downward
|
||||||
|
}
|
||||||
|
return SPIN_HIT_WALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinRenderArt(const FFSpinArt* art, float yaw, float tilt, FFSpinFrame* frame) {
|
||||||
|
frameClear(frame);
|
||||||
|
SpinGeometry g = spinGeometry(art);
|
||||||
|
float rot[3][3], inv[3][3];
|
||||||
|
ffSpinRotationMatrix(yaw, tilt, rot, inv);
|
||||||
|
const float origin[3] = { inv[0][2] * g.camera, inv[1][2] * g.camera, inv[2][2] * g.camera };
|
||||||
|
const uint32_t rampLast = (uint32_t) (sizeof(FFSpinWallRamp) / sizeof(FFSpinWallRamp[0])) - 1;
|
||||||
|
|
||||||
|
for (uint32_t y = 0; y < frame->height; ++y) {
|
||||||
|
float v = (float) frame->height * 0.5f - ((float) y + 0.5f);
|
||||||
|
for (uint32_t x = 0; x < frame->width; ++x) {
|
||||||
|
float u = ((float) x + 0.5f - (float) frame->width * 0.5f) * FF_SPIN_CELL_ASPECT;
|
||||||
|
float d[3];
|
||||||
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
|
d[i] = inv[i][0] * u + inv[i][1] * v - inv[i][2] * g.focal;
|
||||||
|
}
|
||||||
|
uint32_t cell = 0;
|
||||||
|
float n[3];
|
||||||
|
SpinHit hit = castRay(art, &g, origin, d, &cell, n);
|
||||||
|
if (hit == SPIN_HIT_NONE) continue;
|
||||||
|
|
||||||
|
size_t index = (size_t) y * frame->width + x;
|
||||||
|
frame->colors[index] = art->colors[cell];
|
||||||
|
if (hit == SPIN_HIT_FRONT) {
|
||||||
|
frame->glyphs[index] = art->glyphs[cell];
|
||||||
|
} else if (hit == SPIN_HIT_BACK) {
|
||||||
|
frame->glyphs[index] = ffSpinMirrorGlyph(art->glyphs[cell]);
|
||||||
|
} else {
|
||||||
|
float diffuse = 0.0f;
|
||||||
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
|
diffuse += (rot[i][0] * n[0] + rot[i][1] * n[1] + rot[i][2] * n[2]) * FFSpinLight[i];
|
||||||
|
}
|
||||||
|
float luminance = 0.15f + 0.85f * fmaxf(diffuse, 0.0f);
|
||||||
|
frame->glyphs[index] = FFSpinWallRamp[(uint32_t) (luminance * (float) rampLast + 0.5f)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void appendSgr(FFstrbuf* out, const FFSpinArt* art, uint8_t color, bool bold) {
|
||||||
|
ffStrbufAppendS(out, bold ? "\e[0;1" : "\e[0");
|
||||||
|
if (color < art->paletteCount && art->palette[color].length > 0) {
|
||||||
|
ffStrbufAppendC(out, ';');
|
||||||
|
ffStrbufAppend(out, &art->palette[color]);
|
||||||
|
}
|
||||||
|
ffStrbufAppendC(out, 'm');
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinAppendCells(FFstrbuf* out, const FFSpinFrame* frame, const FFSpinArt* art, uint32_t index, uint32_t count, bool color, bool bold) {
|
||||||
|
int32_t active = -1;
|
||||||
|
for (uint32_t i = index; i < index + count; ++i) {
|
||||||
|
uint32_t glyph = frame->glyphs[i];
|
||||||
|
if (glyph == 0) {
|
||||||
|
ffStrbufAppendC(out, ' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (color && (int32_t) frame->colors[i] != active) {
|
||||||
|
active = frame->colors[i];
|
||||||
|
appendSgr(out, art, frame->colors[i], bold);
|
||||||
|
}
|
||||||
|
ffStrbufAppendUtf32CodePoint(out, glyph);
|
||||||
|
}
|
||||||
|
if (active >= 0) {
|
||||||
|
ffStrbufAppendS(out, "\e[0m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinFrameToAnsi(const FFSpinFrame* frame, const FFSpinArt* art, bool color, bool bold, bool trimRight, FFstrbuf* out) {
|
||||||
|
for (uint32_t y = 0; y < frame->height; ++y) {
|
||||||
|
uint32_t length = frame->width;
|
||||||
|
if (trimRight) {
|
||||||
|
// Empty cells at the end of a line are not part of the art. Printing
|
||||||
|
// them would pad every line to the frame width and make the output
|
||||||
|
// differ from the other text logo types.
|
||||||
|
while (length > 0 && frame->glyphs[(size_t) y * frame->width + length - 1] == 0) {
|
||||||
|
--length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffSpinAppendCells(out, frame, art, y * frame->width, length, color, bold);
|
||||||
|
ffStrbufAppendC(out, '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/FFstrbuf.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define FF_SPIN_PALETTE_MAX 32
|
||||||
|
// Palette slots 1..9 hold the logo's $1..$9 colors; raw SGR sequences found in
|
||||||
|
// custom logo files get the slots after that. Slot 0 is the terminal default.
|
||||||
|
#define FF_SPIN_PALETTE_LOGO_SLOTS 10
|
||||||
|
|
||||||
|
// A logo's ASCII art as a grid of cells. The renderer extrudes it into a slab.
|
||||||
|
typedef struct FFSpinArt {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t* glyphs; // Unicode code point per cell, 0 = empty
|
||||||
|
uint8_t* colors; // palette index per cell
|
||||||
|
uint32_t paletteCount;
|
||||||
|
FFstrbuf palette[FF_SPIN_PALETTE_MAX]; // SGR parameters, e.g. "1;34"
|
||||||
|
} FFSpinArt;
|
||||||
|
|
||||||
|
typedef struct FFSpinFrame {
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
uint32_t* glyphs;
|
||||||
|
uint8_t* colors;
|
||||||
|
} FFSpinFrame;
|
||||||
|
|
||||||
|
// `colors` holds the logo's $1..$9 colors (may be nullptr). Returns false for
|
||||||
|
// empty art or when memory runs out.
|
||||||
|
bool ffSpinArtParse(FFSpinArt* art, const char* text, const FFstrbuf* colors, uint32_t colorCount);
|
||||||
|
void ffSpinArtDestroy(FFSpinArt* art);
|
||||||
|
|
||||||
|
bool ffSpinFrameInit(FFSpinFrame* frame, uint32_t width, uint32_t height);
|
||||||
|
void ffSpinFrameDestroy(FFSpinFrame* frame);
|
||||||
|
|
||||||
|
// Frame size that keeps the art inside the frame at every yaw for this tilt.
|
||||||
|
// The art is centered; the extra space is the same on both sides.
|
||||||
|
void ffSpinFrameSize(const FFSpinArt* art, float tilt, uint32_t* width, uint32_t* height);
|
||||||
|
// Copies the art face-on into the center of the frame, exactly as authored.
|
||||||
|
void ffSpinFramePutArt(FFSpinFrame* frame, const FFSpinArt* art);
|
||||||
|
void ffSpinRenderArt(const FFSpinArt* art, float yaw, float tilt, FFSpinFrame* frame);
|
||||||
|
|
||||||
|
void ffSpinRotationMatrix(float yaw, float tilt, float out[3][3], float outInverse[3][3]);
|
||||||
|
bool ffSpinRotationIsOrthonormal(const float matrix[3][3], float epsilon);
|
||||||
|
uint32_t ffSpinMirrorGlyph(uint32_t glyph);
|
||||||
|
|
||||||
|
// Appends `count` cells starting at `index` with colors, ending in a reset.
|
||||||
|
void ffSpinAppendCells(FFstrbuf* out, const FFSpinFrame* frame, const FFSpinArt* art, uint32_t index, uint32_t count, bool color, bool bold);
|
||||||
|
// One line per frame row, each ending in '\n'. `trimRight` drops the empty cells at the end of a
|
||||||
|
// row, which is what the other text logo types print; a frame that is redrawn as it turns must
|
||||||
|
// keep its full width instead, or the information column would move with the angle.
|
||||||
|
void ffSpinFrameToAnsi(const FFSpinFrame* frame, const FFSpinArt* art, bool color, bool bold, bool trimRight, FFstrbuf* out);
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
#include "logo/spin/spin.h"
|
||||||
|
|
||||||
|
#include "common/io.h"
|
||||||
|
#include "common/strutil.h"
|
||||||
|
#include "common/time.h"
|
||||||
|
#include "logo/logo.h"
|
||||||
|
#include "logo/spin/render.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Degrees per frame when neither speed option is set.
|
||||||
|
#define SPIN_DEFAULT_DEGREES_PER_FRAME 10.0f
|
||||||
|
|
||||||
|
// One step of the turn. `logo.spin.speedPerRound` wins over
|
||||||
|
// `logo.spin.speedPerFrame` when both are set; 0 means unset for either.
|
||||||
|
static float spinStepDegrees(void) {
|
||||||
|
const FFOptionsLogoSpin* spin = &instance.config.logo.spin;
|
||||||
|
if (spin->speedPerRound != 0.0f) {
|
||||||
|
// Seconds per revolution: follow the clock instead of counting frames,
|
||||||
|
// so the turn keeps its pace even when a refresh takes longer than the
|
||||||
|
// interval asked for.
|
||||||
|
FFLogoSpinState* state = &instance.state.logoSpin;
|
||||||
|
const double now = ffTimeGetTick();
|
||||||
|
const double elapsedMs = now - state->lastDrawMs;
|
||||||
|
state->lastDrawMs = now;
|
||||||
|
return (float) (360.0 / (double) spin->speedPerRound * elapsedMs / 1000.0);
|
||||||
|
}
|
||||||
|
if (spin->speedPerFrame != 0.0f) {
|
||||||
|
return spin->speedPerFrame;
|
||||||
|
}
|
||||||
|
return SPIN_DEFAULT_DEGREES_PER_FRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool spinIsAuto(const FFstrbuf* source) {
|
||||||
|
return source->length == 0 || ffStrbufIgnCaseEqualS(source, "auto");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FFlogo* spinBuiltin(const FFstrbuf* source, FFLogoSize size) {
|
||||||
|
return spinIsAuto(source) ? ffLogoGetBuiltinDetected(size) : ffLogoGetBuiltinForName(source, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool spinLoadFile(const FFstrbuf* source, FFstrbuf* text) {
|
||||||
|
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
|
||||||
|
if (ffPathExpandEnv(source->chars, &path) && ffAppendFileBuffer(path.chars, text)) {
|
||||||
|
return text->length > 0;
|
||||||
|
}
|
||||||
|
return ffAppendFileBuffer(source->chars, text) && text->length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ffSpinLogoPrint(void) {
|
||||||
|
FFOptionsLogo* options = &instance.config.logo;
|
||||||
|
FFLogoSpinState* state = &instance.state.logoSpin;
|
||||||
|
instance.state.logoWidth = 0;
|
||||||
|
instance.state.logoHeight = 0;
|
||||||
|
|
||||||
|
if (!state->started) {
|
||||||
|
// The first frame starts where `logo.spin.angle` says, and every
|
||||||
|
// `--watch` refresh turns it by one step.
|
||||||
|
state->started = true;
|
||||||
|
state->degrees = fmodf(options->spin.angle, 360.0f);
|
||||||
|
if (state->degrees < 0.0f) {
|
||||||
|
state->degrees += 360.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const float spinAngle = state->degrees * (float) M_PI / 180.0f;
|
||||||
|
|
||||||
|
// The source is the detected OS (`auto`), a builtin logo name, or a file
|
||||||
|
// in fastfetch's logo syntax, like the other text logo types.
|
||||||
|
FF_STRBUF_AUTO_DESTROY fileText = ffStrbufCreate();
|
||||||
|
const FFlogo* logo = spinBuiltin(&options->source, FF_LOGO_SIZE_NORMAL);
|
||||||
|
if (!logo) {
|
||||||
|
logo = spinBuiltin(&options->source, FF_LOGO_SIZE_UNKNOWN);
|
||||||
|
}
|
||||||
|
if (!logo && !spinLoadFile(&options->source, &fileText)) {
|
||||||
|
if (instance.config.display.showErrors) {
|
||||||
|
fprintf(stderr, "Logo (spin): no builtin logo or readable file named %s\n", options->source.chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// `logo.color.N` on top of the logo's own colors; a file logo borrows the
|
||||||
|
// detected logo's palette, as the other text logo types do.
|
||||||
|
ffLogoApplyColors(logo ? logo : ffLogoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), true);
|
||||||
|
|
||||||
|
FFSpinArt art = {};
|
||||||
|
if (!ffSpinArtParse(&art, logo ? logo->lines : fileText.chars, options->colors, FASTFETCH_LOGO_MAX_COLORS)) {
|
||||||
|
ffSpinArtDestroy(&art); // parse may have allocated before it gave up
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float tilt = options->spin.tilt * (float) M_PI / 180.0f;
|
||||||
|
uint32_t width = art.width;
|
||||||
|
uint32_t height = art.height;
|
||||||
|
// With `--watch` the frame has to stay the same size from the first draw on,
|
||||||
|
// or the information column would move when the logo starts turning.
|
||||||
|
const bool animated = instance.state.dynamicInterval > 0;
|
||||||
|
if (animated || spinAngle != 0.0f || tilt != 0.0f) {
|
||||||
|
// A turned slab sticks out of the art's box, so the frame has to make
|
||||||
|
// room for its widest angle or the logo would be clipped.
|
||||||
|
ffSpinFrameSize(&art, tilt, &width, &height);
|
||||||
|
}
|
||||||
|
|
||||||
|
FFSpinFrame frame = {};
|
||||||
|
if (!ffSpinFrameInit(&frame, width, height)) {
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spinAngle == 0.0f && tilt == 0.0f) {
|
||||||
|
ffSpinFramePutArt(&frame, &art); // face on: exactly the original art
|
||||||
|
} else {
|
||||||
|
ffSpinRenderArt(&art, spinAngle, tilt, &frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hand the frame to the upstream logo pipeline: it pads, positions and
|
||||||
|
// measures the art, and its line cache is what puts the modules next to
|
||||||
|
// the logo. `false`: the frame already carries its own SGR sequences.
|
||||||
|
FF_STRBUF_AUTO_DESTROY ansi = ffStrbufCreateA(width * height * 4);
|
||||||
|
ffSpinFrameToAnsi(&frame, &art, !instance.config.display.pipe, instance.config.display.brightColor, !animated, &ansi);
|
||||||
|
// The serialiser ends every row with a newline; the last one would add an
|
||||||
|
// empty line to the logo and change the output from the other logo types.
|
||||||
|
if (ansi.length > 0 && ansi.chars[ansi.length - 1] == '\n') {
|
||||||
|
ansi.chars[--ansi.length] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// A resize changes the frame size, and the cells outside the new frame
|
||||||
|
// would keep showing the previous one.
|
||||||
|
if (state->printed && (width != state->printedWidth || height != state->printedHeight)) {
|
||||||
|
fputs("\e[2J\e[H", stdout);
|
||||||
|
}
|
||||||
|
// `ffLogoPrintChars` writes the art with write(2) for `--logo-position top`,
|
||||||
|
// while the caller's cursor moves go through stdio; flush so the art lands
|
||||||
|
// where the cursor was just moved to.
|
||||||
|
fflush(stdout);
|
||||||
|
ffLogoPrintChars(ansi.chars, false);
|
||||||
|
|
||||||
|
ffSpinFrameDestroy(&frame);
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
|
||||||
|
state->printed = true;
|
||||||
|
state->printedWidth = width;
|
||||||
|
state->printedHeight = height;
|
||||||
|
state->lastDrawMs = ffTimeGetTick(); // start measuring the next step from now
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ffSpinTick(void) {
|
||||||
|
FFLogoSpinState* state = &instance.state.logoSpin;
|
||||||
|
if (!state->printed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float step = spinStepDegrees();
|
||||||
|
if (step == 0.0f) {
|
||||||
|
return false; // a logo that does not turn does not have to be redrawn
|
||||||
|
}
|
||||||
|
|
||||||
|
// The angle is wrapped instead of growing, or a long `--watch` session
|
||||||
|
// would lose precision in the sine and cosine.
|
||||||
|
float degrees = state->degrees + step;
|
||||||
|
state->degrees = degrees - floorf(degrees / 360.0f) * 360.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// The spin logo: the builtin logo (or a text file in logo syntax) drawn as an
|
||||||
|
// extruded slab. It has no timer and reads no input of its own; the `--watch`
|
||||||
|
// loop calls `ffSpinTick` once per refresh, which is what makes it turn.
|
||||||
|
|
||||||
|
// Draws the current frame. Called from `ffLogoPrint`.
|
||||||
|
void ffSpinLogoPrint(void);
|
||||||
|
|
||||||
|
// Advances the rotation by one `--watch` refresh and returns true when the logo
|
||||||
|
// has to be drawn again, so a still logo or any other logo type costs a single
|
||||||
|
// comparison per refresh. The step is `logo.spin.speedPerFrame` degrees, or
|
||||||
|
// whatever `logo.spin.speedPerRound` makes of the time since the last frame.
|
||||||
|
bool ffSpinTick(void);
|
||||||
@@ -23,6 +23,22 @@ static bool logoTypeIsImage(FFLogoType type) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// The numeric `--logo-spin-*` options: a plain number with a range, so a typo is
|
||||||
|
// rejected instead of producing a nonsensical frame.
|
||||||
|
static float logoParseFloat(const char* key, const char* value, float min, float max) {
|
||||||
|
if (value == nullptr) {
|
||||||
|
fprintf(stderr, "Error: usage: %s <number>\n", key);
|
||||||
|
exit(477);
|
||||||
|
}
|
||||||
|
char* end = nullptr;
|
||||||
|
double parsed = strtod(value, &end);
|
||||||
|
if (end == value || *end != '\0' || parsed < min || parsed > max) {
|
||||||
|
fprintf(stderr, "Error: %s must be a number between %g and %g\n", key, min, max);
|
||||||
|
exit(477);
|
||||||
|
}
|
||||||
|
return (float) parsed;
|
||||||
|
}
|
||||||
|
|
||||||
// --sixel, --kitty, --kitty-direct, --kitty-icat and --iterm. They keep a branch of their own in
|
// --sixel, --kitty, --kitty-direct, --kitty-icat and --iterm. They keep a branch of their own in
|
||||||
// every build so that the error a build without image logos gives names the flag that was used,
|
// every build so that the error a build without image logos gives names the flag that was used,
|
||||||
// rather than it being rejected as an unknown option. --chafa is not among them: it is gated on
|
// rather than it being rejected as an unknown option. --chafa is not among them: it is gated on
|
||||||
@@ -70,6 +86,10 @@ void ffOptionsInitLogo(FFOptionsLogo* options) {
|
|||||||
options->cache = FF_LOGO_CACHE_ON;
|
options->cache = FF_LOGO_CACHE_ON;
|
||||||
options->position = FF_LOGO_POSITION_LEFT;
|
options->position = FF_LOGO_POSITION_LEFT;
|
||||||
options->animationFrame = FF_LOGO_ANIMATION_FRAME_FIRST;
|
options->animationFrame = FF_LOGO_ANIMATION_FRAME_FIRST;
|
||||||
|
options->spin.speedPerFrame = 0.0f; // 0: not set, see `ffSpinTick`
|
||||||
|
options->spin.speedPerRound = 0.0f; // 0: not set, see `ffSpinTick`
|
||||||
|
options->spin.tilt = 0.0f;
|
||||||
|
options->spin.angle = 0.0f;
|
||||||
|
|
||||||
#if FF_HAVE_CHAFA
|
#if FF_HAVE_CHAFA
|
||||||
options->chafaFgOnly = false;
|
options->chafaFgOnly = false;
|
||||||
@@ -118,6 +138,7 @@ bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, cons
|
|||||||
{ "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
|
{ "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
|
||||||
{ "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
|
{ "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
|
||||||
{ "raw", FF_LOGO_TYPE_IMAGE_RAW },
|
{ "raw", FF_LOGO_TYPE_IMAGE_RAW },
|
||||||
|
{ "spin", FF_LOGO_TYPE_SPIN },
|
||||||
{ "none", FF_LOGO_TYPE_NONE },
|
{ "none", FF_LOGO_TYPE_NONE },
|
||||||
{},
|
{},
|
||||||
});
|
});
|
||||||
@@ -184,6 +205,14 @@ bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, cons
|
|||||||
});
|
});
|
||||||
} else if (ffStrEqualsIgnCase(subKey, "animation-frame")) {
|
} else if (ffStrEqualsIgnCase(subKey, "animation-frame")) {
|
||||||
options->animationFrame = ffOptionParseInt32(key, value);
|
options->animationFrame = ffOptionParseInt32(key, value);
|
||||||
|
} else if (ffStrEqualsIgnCase(subKey, "spin-speed-per-frame")) {
|
||||||
|
options->spin.speedPerFrame = logoParseFloat(key, value, -360.0f, 360.0f);
|
||||||
|
} else if (ffStrEqualsIgnCase(subKey, "spin-speed-per-round")) {
|
||||||
|
options->spin.speedPerRound = logoParseFloat(key, value, -3600.0f, 3600.0f);
|
||||||
|
} else if (ffStrEqualsIgnCase(subKey, "spin-tilt")) {
|
||||||
|
options->spin.tilt = logoParseFloat(key, value, -80.0f, 80.0f);
|
||||||
|
} else if (ffStrEqualsIgnCase(subKey, "spin-angle")) {
|
||||||
|
options->spin.angle = logoParseFloat(key, value, 0.0f, 360.0f);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -323,6 +352,7 @@ const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* roo
|
|||||||
{ "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
|
{ "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
|
||||||
{ "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
|
{ "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
|
||||||
{ "raw", FF_LOGO_TYPE_IMAGE_RAW },
|
{ "raw", FF_LOGO_TYPE_IMAGE_RAW },
|
||||||
|
{ "spin", FF_LOGO_TYPE_SPIN },
|
||||||
{ "none", FF_LOGO_TYPE_NONE },
|
{ "none", FF_LOGO_TYPE_NONE },
|
||||||
{},
|
{},
|
||||||
});
|
});
|
||||||
@@ -436,6 +466,50 @@ const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* roo
|
|||||||
}
|
}
|
||||||
options->animationFrame = (int32_t) yyjson_get_sint(val);
|
options->animationFrame = (int32_t) yyjson_get_sint(val);
|
||||||
continue;
|
continue;
|
||||||
|
} else if (unsafe_yyjson_equals_str(key, "spin")) {
|
||||||
|
if (!yyjson_is_obj(val)) {
|
||||||
|
return "Property 'logo.spin' must be an object";
|
||||||
|
}
|
||||||
|
|
||||||
|
yyjson_val *spinKey, *spinVal;
|
||||||
|
size_t spinIdx, spinMax;
|
||||||
|
yyjson_obj_foreach (val, spinIdx, spinMax, spinKey, spinVal) {
|
||||||
|
if (unsafe_yyjson_equals_str(spinKey, "speed")) {
|
||||||
|
if (!yyjson_is_obj(spinVal)) {
|
||||||
|
return "Property 'logo.spin.speed' must be an object";
|
||||||
|
}
|
||||||
|
yyjson_val *speedKey, *speedVal;
|
||||||
|
size_t speedIdx, speedMax;
|
||||||
|
yyjson_obj_foreach (spinVal, speedIdx, speedMax, speedKey, speedVal) {
|
||||||
|
if (unsafe_yyjson_equals_str(speedKey, "perFrame")) {
|
||||||
|
if (!yyjson_is_num(speedVal) || yyjson_get_num(speedVal) < -360 || yyjson_get_num(speedVal) > 360) {
|
||||||
|
return "Property 'logo.spin.speed.perFrame' must be a number between -360 and 360";
|
||||||
|
}
|
||||||
|
options->spin.speedPerFrame = (float) yyjson_get_num(speedVal);
|
||||||
|
} else if (unsafe_yyjson_equals_str(speedKey, "perRound")) {
|
||||||
|
if (!yyjson_is_num(speedVal) || yyjson_get_num(speedVal) < -3600 || yyjson_get_num(speedVal) > 3600) {
|
||||||
|
return "Property 'logo.spin.speed.perRound' must be a number between -3600 and 3600";
|
||||||
|
}
|
||||||
|
options->spin.speedPerRound = (float) yyjson_get_num(speedVal);
|
||||||
|
} else {
|
||||||
|
return "Unknown logo.spin.speed key";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (unsafe_yyjson_equals_str(spinKey, "tilt")) {
|
||||||
|
if (!yyjson_is_num(spinVal) || yyjson_get_num(spinVal) < -80 || yyjson_get_num(spinVal) > 80) {
|
||||||
|
return "Property 'logo.spin.tilt' must be a number between -80 and 80";
|
||||||
|
}
|
||||||
|
options->spin.tilt = (float) yyjson_get_num(spinVal);
|
||||||
|
} else if (unsafe_yyjson_equals_str(spinKey, "angle")) {
|
||||||
|
if (!yyjson_is_num(spinVal) || yyjson_get_num(spinVal) < 0 || yyjson_get_num(spinVal) > 360) {
|
||||||
|
return "Property 'logo.spin.angle' must be a number between 0 and 360";
|
||||||
|
}
|
||||||
|
options->spin.angle = (float) yyjson_get_num(spinVal);
|
||||||
|
} else {
|
||||||
|
return "Unknown logo.spin key";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
} else if (unsafe_yyjson_equals_str(key, "chafa")) {
|
} else if (unsafe_yyjson_equals_str(key, "chafa")) {
|
||||||
#if FF_HAVE_CHAFA
|
#if FF_HAVE_CHAFA
|
||||||
if (!yyjson_is_obj(val)) {
|
if (!yyjson_is_obj(val)) {
|
||||||
@@ -568,6 +642,9 @@ void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) {
|
|||||||
case FF_LOGO_TYPE_IMAGE_RAW:
|
case FF_LOGO_TYPE_IMAGE_RAW:
|
||||||
yyjson_mut_obj_add_str(doc, obj, "type", "raw");
|
yyjson_mut_obj_add_str(doc, obj, "type", "raw");
|
||||||
break;
|
break;
|
||||||
|
case FF_LOGO_TYPE_SPIN:
|
||||||
|
yyjson_mut_obj_add_str(doc, obj, "type", "spin");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
yyjson_mut_obj_add_str(doc, obj, "type", "auto");
|
yyjson_mut_obj_add_str(doc, obj, "type", "auto");
|
||||||
break;
|
break;
|
||||||
@@ -575,6 +652,17 @@ void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) {
|
|||||||
|
|
||||||
yyjson_mut_obj_add_str(doc, obj, "source", options->source.chars);
|
yyjson_mut_obj_add_str(doc, obj, "source", options->source.chars);
|
||||||
|
|
||||||
|
if (options->type == FF_LOGO_TYPE_SPIN) {
|
||||||
|
yyjson_mut_val* spin = yyjson_mut_obj(doc);
|
||||||
|
yyjson_mut_val* speed = yyjson_mut_obj(doc);
|
||||||
|
yyjson_mut_obj_add_real(doc, speed, "perFrame", options->spin.speedPerFrame);
|
||||||
|
yyjson_mut_obj_add_real(doc, speed, "perRound", options->spin.speedPerRound);
|
||||||
|
yyjson_mut_obj_add_val(doc, spin, "speed", speed);
|
||||||
|
yyjson_mut_obj_add_real(doc, spin, "tilt", options->spin.tilt);
|
||||||
|
yyjson_mut_obj_add_real(doc, spin, "angle", options->spin.angle);
|
||||||
|
yyjson_mut_obj_add_val(doc, obj, "spin", spin);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
yyjson_mut_val* color = yyjson_mut_obj(doc);
|
yyjson_mut_val* color = yyjson_mut_obj(doc);
|
||||||
for (int i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
|
for (int i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
|
||||||
|
|||||||
@@ -21,9 +21,17 @@ typedef enum FFLogoType: uint8_t {
|
|||||||
FF_LOGO_TYPE_IMAGE_ITERM, // image file, printed as iterm graphics protocol
|
FF_LOGO_TYPE_IMAGE_ITERM, // image file, printed as iterm graphics protocol
|
||||||
FF_LOGO_TYPE_IMAGE_CHAFA, // image file, printed as ascii art using libchafa
|
FF_LOGO_TYPE_IMAGE_CHAFA, // image file, printed as ascii art using libchafa
|
||||||
FF_LOGO_TYPE_IMAGE_RAW, // image file, printed as raw binary string
|
FF_LOGO_TYPE_IMAGE_RAW, // image file, printed as raw binary string
|
||||||
|
FF_LOGO_TYPE_SPIN, // builtin logo (or a text file) drawn as a 3D slab, one turn step per `--watch` refresh
|
||||||
FF_LOGO_TYPE_NONE, // `--logo none`, but still applies colors to the system information output (unless `--pipe` is set)
|
FF_LOGO_TYPE_NONE, // `--logo none`, but still applies colors to the system information output (unless `--pipe` is set)
|
||||||
} FFLogoType;
|
} FFLogoType;
|
||||||
|
|
||||||
|
typedef struct FFOptionsLogoSpin {
|
||||||
|
float speedPerFrame; // degrees the logo turns per drawn frame; negative turns the other way, 0: not set
|
||||||
|
float speedPerRound; // seconds one revolution takes; negative turns the other way, 0: not set (wins over speedPerFrame)
|
||||||
|
float tilt; // camera tilt in degrees
|
||||||
|
float angle; // rotation of the first frame in degrees; drawn as is without `--watch`
|
||||||
|
} FFOptionsLogoSpin;
|
||||||
|
|
||||||
typedef enum FFLogoPosition: uint8_t {
|
typedef enum FFLogoPosition: uint8_t {
|
||||||
FF_LOGO_POSITION_LEFT,
|
FF_LOGO_POSITION_LEFT,
|
||||||
FF_LOGO_POSITION_TOP,
|
FF_LOGO_POSITION_TOP,
|
||||||
@@ -47,6 +55,7 @@ typedef struct FFOptionsLogo {
|
|||||||
FFstrbuf source;
|
FFstrbuf source;
|
||||||
FFLogoType type;
|
FFLogoType type;
|
||||||
FFLogoPosition position;
|
FFLogoPosition position;
|
||||||
|
FFOptionsLogoSpin spin;
|
||||||
FFstrbuf colors[FASTFETCH_LOGO_MAX_COLORS];
|
FFstrbuf colors[FASTFETCH_LOGO_MAX_COLORS];
|
||||||
uint32_t width;
|
uint32_t width;
|
||||||
uint32_t height;
|
uint32_t height;
|
||||||
|
|||||||
+224
@@ -0,0 +1,224 @@
|
|||||||
|
#include "fastfetch.h"
|
||||||
|
#include "logo/logo.h"
|
||||||
|
#include "logo/spin/render.h"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Unlike assert(), this also checks in release builds.
|
||||||
|
#define VERIFY(expression) \
|
||||||
|
do { \
|
||||||
|
if (!(expression)) { \
|
||||||
|
fprintf(stderr, "spin test failed [%d]: %s\n", __LINE__, #expression); \
|
||||||
|
exit(1); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
static bool isWallGlyph(uint32_t glyph) {
|
||||||
|
return glyph >= 0x2591 && glyph <= 0x2593 ? true : glyph == 0x2588; // ░▒▓█
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testRotation(void) {
|
||||||
|
const float angles[] = { 0.0f, 0.25f, (float) M_PI / 2.0f, (float) M_PI, 5.0f };
|
||||||
|
const float tilts[] = { 0.0f, 0.2f, -0.4f, 1.0f };
|
||||||
|
for (uint32_t i = 0; i < ARRAY_SIZE(angles); ++i) {
|
||||||
|
for (uint32_t j = 0; j < ARRAY_SIZE(tilts); ++j) {
|
||||||
|
float rotation[3][3], inverse[3][3];
|
||||||
|
ffSpinRotationMatrix(angles[i], tilts[j], rotation, inverse);
|
||||||
|
VERIFY(ffSpinRotationIsOrthonormal(rotation, 1e-4f));
|
||||||
|
for (uint32_t r = 0; r < 3; ++r) {
|
||||||
|
for (uint32_t c = 0; c < 3; ++c) {
|
||||||
|
VERIFY(fabsf(rotation[r][c] - inverse[c][r]) < 1e-5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testParse(void) {
|
||||||
|
FFstrbuf colors[2];
|
||||||
|
ffStrbufInitStatic(&colors[0], "31");
|
||||||
|
ffStrbufInitStatic(&colors[1], "1;34");
|
||||||
|
|
||||||
|
FFSpinArt art;
|
||||||
|
// Leading blank rows and columns are cropped; `$$` is a literal dollar.
|
||||||
|
VERIFY(ffSpinArtParse(&art, "\n $1ab\n $2c$$\xe2\x96\x88\n", colors, 2));
|
||||||
|
VERIFY(art.width == 3 && art.height == 2);
|
||||||
|
VERIFY(art.glyphs[0] == 'a' && art.colors[0] == 1);
|
||||||
|
VERIFY(art.glyphs[1] == 'b' && art.colors[1] == 1);
|
||||||
|
VERIFY(art.glyphs[2] == 0);
|
||||||
|
VERIFY(art.glyphs[3] == 'c' && art.colors[3] == 2);
|
||||||
|
VERIFY(art.glyphs[4] == '$');
|
||||||
|
VERIFY(art.glyphs[5] == 0x2588);
|
||||||
|
VERIFY(ffStrbufEqualS(&art.palette[2], "1;34"));
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
|
||||||
|
// Raw SGR escapes in custom logo files get their own palette slots.
|
||||||
|
VERIFY(ffSpinArtParse(&art, "\e[38;5;208mx\e[0my", nullptr, 0));
|
||||||
|
VERIFY(art.width == 2 && art.height == 1);
|
||||||
|
VERIFY(art.colors[0] >= FF_SPIN_PALETTE_LOGO_SLOTS);
|
||||||
|
VERIFY(ffStrbufEqualS(&art.palette[art.colors[0]], "38;5;208"));
|
||||||
|
VERIFY(art.colors[1] == 0);
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
|
||||||
|
VERIFY(!ffSpinArtParse(&art, " \n \t \n", nullptr, 0));
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
VERIFY(!ffSpinArtParse(&art, "", nullptr, 0));
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testMirror(void) {
|
||||||
|
VERIFY(ffSpinMirrorGlyph('/') == '\\');
|
||||||
|
VERIFY(ffSpinMirrorGlyph('\\') == '/');
|
||||||
|
VERIFY(ffSpinMirrorGlyph('(') == ')');
|
||||||
|
VERIFY(ffSpinMirrorGlyph('o') == 'o');
|
||||||
|
VERIFY(ffSpinMirrorGlyph(0x2801) == 0x2808); // braille dot 1 -> dot 4
|
||||||
|
VERIFY(ffSpinMirrorGlyph(0x2840) == 0x2880); // braille dot 7 -> dot 8
|
||||||
|
for (uint32_t glyph = 0x2800; glyph <= 0x28FF; ++glyph) {
|
||||||
|
VERIFY(ffSpinMirrorGlyph(ffSpinMirrorGlyph(glyph)) == glyph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char testLogo[] =
|
||||||
|
"$1 /\\\n"
|
||||||
|
"$1 / \\\n"
|
||||||
|
"$1 /\\ \\\n"
|
||||||
|
"$2 / __ \\\n"
|
||||||
|
"$2/__/ \\__\\";
|
||||||
|
|
||||||
|
static void testRenderFaces(void) {
|
||||||
|
FFstrbuf colors[2];
|
||||||
|
ffStrbufInitStatic(&colors[0], "36");
|
||||||
|
ffStrbufInitStatic(&colors[1], "34");
|
||||||
|
FFSpinArt art;
|
||||||
|
VERIFY(ffSpinArtParse(&art, testLogo, colors, 2));
|
||||||
|
|
||||||
|
uint32_t width, height;
|
||||||
|
ffSpinFrameSize(&art, 0.0f, &width, &height);
|
||||||
|
VERIFY(width >= art.width && height >= art.height);
|
||||||
|
VERIFY((width - art.width) % 2 == 0 && (height - art.height) % 2 == 0);
|
||||||
|
uint32_t left = (width - art.width) / 2, top = (height - art.height) / 2;
|
||||||
|
|
||||||
|
FFSpinFrame frame;
|
||||||
|
VERIFY(ffSpinFrameInit(&frame, width, height));
|
||||||
|
|
||||||
|
// Face-on, every cell of the art shows up unchanged at its place.
|
||||||
|
ffSpinRenderArt(&art, 0.0f, 0.0f, &frame);
|
||||||
|
for (uint32_t y = 0; y < art.height; ++y) {
|
||||||
|
for (uint32_t x = 0; x < art.width; ++x) {
|
||||||
|
uint32_t glyph = art.glyphs[y * art.width + x];
|
||||||
|
if (!glyph) continue;
|
||||||
|
size_t index = (size_t) (top + y) * width + left + x;
|
||||||
|
VERIFY(frame.glyphs[index] == glyph);
|
||||||
|
VERIFY(frame.colors[index] == art.colors[y * art.width + x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turned around, the back face shows the art mirrored, glyphs included.
|
||||||
|
ffSpinRenderArt(&art, (float) M_PI, 0.0f, &frame);
|
||||||
|
for (uint32_t y = 0; y < art.height; ++y) {
|
||||||
|
for (uint32_t x = 0; x < art.width; ++x) {
|
||||||
|
uint32_t glyph = art.glyphs[y * art.width + x];
|
||||||
|
if (!glyph) continue;
|
||||||
|
size_t index = (size_t) (top + y) * width + left + (art.width - 1 - x);
|
||||||
|
VERIFY(frame.glyphs[index] == ffSpinMirrorGlyph(glyph));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edge-on, only a thin band of edge shading is left.
|
||||||
|
ffSpinRenderArt(&art, (float) M_PI / 2.0f, 0.0f, &frame);
|
||||||
|
uint32_t usedColumns = 0;
|
||||||
|
for (uint32_t x = 0; x < width; ++x) {
|
||||||
|
bool used = false;
|
||||||
|
for (uint32_t y = 0; y < height; ++y) {
|
||||||
|
uint32_t glyph = frame.glyphs[y * width + x];
|
||||||
|
if (glyph) {
|
||||||
|
used = true;
|
||||||
|
VERIFY(isWallGlyph(glyph));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usedColumns += used;
|
||||||
|
}
|
||||||
|
VERIFY(usedColumns > 0 && usedColumns <= 4);
|
||||||
|
|
||||||
|
ffSpinFrameDestroy(&frame);
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders into a frame one cell larger on every side than ffSpinFrameSize
|
||||||
|
// asks for; the extra ring must stay empty at every angle.
|
||||||
|
static void verifyFits(const FFSpinArt* art, float tilt) {
|
||||||
|
uint32_t width, height;
|
||||||
|
ffSpinFrameSize(art, tilt, &width, &height);
|
||||||
|
FFSpinFrame frame;
|
||||||
|
VERIFY(ffSpinFrameInit(&frame, width + 2, height + 2));
|
||||||
|
for (uint32_t step = 0; step < 48; ++step) {
|
||||||
|
ffSpinRenderArt(art, (float) step * 2.0f * (float) M_PI / 48.0f, tilt, &frame);
|
||||||
|
for (uint32_t x = 0; x < frame.width; ++x) {
|
||||||
|
VERIFY(frame.glyphs[x] == 0);
|
||||||
|
VERIFY(frame.glyphs[(size_t) (frame.height - 1) * frame.width + x] == 0);
|
||||||
|
}
|
||||||
|
for (uint32_t y = 0; y < frame.height; ++y) {
|
||||||
|
VERIFY(frame.glyphs[(size_t) y * frame.width] == 0);
|
||||||
|
VERIFY(frame.glyphs[(size_t) y * frame.width + frame.width - 1] == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffSpinFrameDestroy(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testBuiltinLogos(void) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (uint32_t letter = 0; letter < 26; ++letter) {
|
||||||
|
for (const FFlogo* logo = ffLogoBuiltins[letter]; *logo->names; ++logo) {
|
||||||
|
FFSpinArt art;
|
||||||
|
VERIFY(ffSpinArtParse(&art, logo->lines, nullptr, 0));
|
||||||
|
VERIFY(art.width > 0 && art.height > 0);
|
||||||
|
verifyFits(&art, 0.0f);
|
||||||
|
if (count % 16 == 0) {
|
||||||
|
verifyFits(&art, 0.4f);
|
||||||
|
}
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VERIFY(count > 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testAnsiOutput(void) {
|
||||||
|
FFstrbuf colors[1];
|
||||||
|
ffStrbufInitStatic(&colors[0], "35");
|
||||||
|
FFSpinArt art;
|
||||||
|
VERIFY(ffSpinArtParse(&art, "a\xe2\x96\x88 b", colors, 1));
|
||||||
|
FFSpinFrame frame;
|
||||||
|
VERIFY(ffSpinFrameInit(&frame, art.width, art.height));
|
||||||
|
ffSpinFramePutArt(&frame, &art);
|
||||||
|
|
||||||
|
FF_STRBUF_AUTO_DESTROY plain = ffStrbufCreate();
|
||||||
|
ffSpinFrameToAnsi(&frame, &art, false, false, true, &plain);
|
||||||
|
VERIFY(ffStrbufEqualS(&plain, "a\xe2\x96\x88 b\n"));
|
||||||
|
|
||||||
|
FF_STRBUF_AUTO_DESTROY colored = ffStrbufCreate();
|
||||||
|
ffSpinFrameToAnsi(&frame, &art, true, true, true, &colored);
|
||||||
|
VERIFY(strstr(colored.chars, "\e[0;1;35ma") != nullptr);
|
||||||
|
VERIFY(strstr(colored.chars, "\e[0m\n") != nullptr);
|
||||||
|
|
||||||
|
ffSpinFrameDestroy(&frame);
|
||||||
|
ffSpinArtDestroy(&art);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
testRotation();
|
||||||
|
testParse();
|
||||||
|
testMirror();
|
||||||
|
testRenderFaces();
|
||||||
|
testBuiltinLogos();
|
||||||
|
testAnsiOutput();
|
||||||
|
puts("spin tests passed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user