Compare commits

...

18 Commits

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

| Package | From | To |
| --- | --- | --- |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` |
| [cross-platform-actions/action](https://github.com/cross-platform-actions/action) | `1.6.0` | `1.7.0` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.2` | `4.39.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.2` | `4.39.1` |
| [vmactions/solaris-vm](https://github.com/vmactions/solaris-vm) | `1.4.1` | `1.4.2` |



Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

Updates `cross-platform-actions/action` from 1.6.0 to 1.7.0
- [Release notes](https://github.com/cross-platform-actions/action/releases)
- [Changelog](https://github.com/cross-platform-actions/action/blob/master/changelog.md)
- [Commits](https://github.com/cross-platform-actions/action/compare/e0b9770014ba65d5e0815f15b74031c3a635f641...82668f1fbd89d9c99487604f822cbf6716f124d3)

Updates `github/codeql-action/init` from 4.38.2 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

Updates `github/codeql-action/analyze` from 4.38.2 to 4.39.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

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

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: cross-platform-actions/action
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: vmactions/solaris-vm
  dependency-version: 1.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-01 18:11:14 +00:00
Mohd Quamar Tyagi aeb22b4081 Logo (Builtin): adds ArmadaOS (#2649)
* Logo: add ArmadaOS builtin ASCII logo

Closes #2641

* Change logo name from 'armada' to 'Armada'

* Update ASCII art in armada.txt

---------

Co-authored-by: Abhipsita-coder <76418226+Abhipsita-coder@users.noreply.github.com>
Co-authored-by: Carter Li <CarterLi@users.noreply.github.com>
2026-11-01 16:33:57 +08:00
linux-universe 2c99d020ec Logo (Builtin): adds AstrOS logo (#2582)
* Logo: add AstrOS

* Update ASCII art in astros.txt

---------

Co-authored-by: Carter Li <CarterLi@users.noreply.github.com>
2026-11-01 16:24:35 +08:00
arxhr007 d697197a8b Format: fixes substring with an end index outside of the string
`{1~20,30}` printed the placeholder as is, because the `,end` part was only
checked when the start index was inside the string. `{1~20}` printed nothing.
Parse and check the whole range first, so that a valid range that lies outside
of the string gives an empty result.
2026-10-31 22:40:07 +08:00
arxhr007 d6c15e1cec Format: fixes integer wraparound when parsing numbers
Numbers that don't fit in 32 bits were truncated, so `{4294967297}` was read
as `{1}`, `{4294967296}` as the next argument, and `{$4294967297}` as the
first constant. The same applied to the start, end and length of `~`, `:`, `<`,
`>` and `|`. Clamp them instead, so that they stay out of range.

Also avoid negating INT32_MIN for the ellipsis length of `{1:-2147483648}`,
which is a signed integer overflow.
2026-10-31 22:08:40 +08:00
arxhr007 d4dd15763f Format: fixes skipping nested conditionals
When a `{?arg}` / `{/arg}` conditional is skipped, the end of the skipped part
was the next `{?}` / `{/}` in the format string, even if it closed a
conditional nested in it. The text after the nested conditional was printed
and the real closing `{?}` / `{/}` was reported as an invalid placeholder.
A literal `{{?}}` inside the skipped part also ended it.

Skip to the `{?}` / `{/}` that matches the opening one, like the printing path
already does.
2026-10-31 17:26:01 +08:00
Carter Li 7d5174a489 Doc: updates changelog [ci skip] 2026-10-31 14:50:24 +08:00
mishalshanavas 1ee988a698 CI: pins musl amd64 builds to Ubuntu 24.04 2026-10-31 14:18:05 +08:00
Kushagra Kumar 2eea01aac3 GPU (Linux): pad device and revision IDs when querying amdgpu.ids
In libdrm/amdgpu.ids, device IDs are formatted with 4 hex digits and
revision IDs with 2 uppercase hex digits (e.g. `1681,\t0B,`). Using
`%04X,\t%02X,` ensures both fields match the file syntax even when
values have leading zeros.
2026-10-31 08:59:22 +08:00
Carter Li 44b32e9f84 Separator: rejects empty string
Fixes #2689 #2690
2026-10-30 08:47:24 +08:00
Carter Li 06957877a8 Wifi (macOS): adds comments 2026-10-29 14:05:41 +08:00
ihsandeniz 2f68ce7f74 IO (Windows): fixes blocking forever on incomplete terminal responses
Same issue as on Unix: ffGetTerminalResponse() waits for input only before
the first ReadFile(). If the terminal answers only part of the request
(e.g. one of the two OSC 10/11 replies), the next ReadFile() blocks forever.

Wait with the same timeout before every subsequent ReadFile() as well, and
restore the console mode on that error path. While waiting, also skip
key-up records: ReadFile() returns no data for them, so one left alone in
the buffer would end the wait and the following ReadFile() would block.
2026-10-28 09:17:24 +08:00
ihsandeniz 408646df1c IO (Unix): fixes blocking forever on incomplete terminal responses
ffGetTerminalResponse() polls only before the first read(). If the terminal
sends a response that is cut short (e.g. `\e[4;800` without the rest), the
following read() in the loop blocks forever because the tty is in
non-canonical mode with VMIN=1.

Wait with the same timeout before every subsequent read() as well.
2026-10-28 09:17:24 +08:00
Don Yin ad62e3d1f5 Format: fixes substring ends before the start of the string (#2669)
* Format: fix substring ends before the start of the string

Closes #2668

Signed-off-by: Don Yin <Don_Yin@outlook.com>

* Doc: moves changelog entry to Unreleased [ci skip]

I moved the unreleased fix out of the 2.70.0 section.

Signed-off-by: Don Yin <Don_Yin@outlook.com>

---------

Signed-off-by: Don Yin <Don_Yin@outlook.com>
2026-10-26 00:34:50 -05:00
Carter Li 197d1c6e5d Uptime (SunOS): improves detection via kstat 2026-10-25 00:13:54 +08:00
berce bfe74b8437 Logo: treat a "$" ending a line or the logo as a plain dollar (#2667)
Fixes #2666
2026-10-23 02:28:06 -05:00
Carter Li bbb92543b0 Display (Wayland): uses DisplayConfig on GNOME if available 2026-10-17 10:23:13 +08:00
UnixUserDev 99785f54dd Logo (Builtin): add Tiny Core Linux (#2655)
* Logo (Builtin): add Tiny Core Linux

* Logo (Builtin): use only the TinyCore name for Tiny Core

---------

Co-authored-by: UnixUserDev <172555708+UnixUserDev@users.noreply.github.com>
2026-10-16 16:41:12 +08:00
40 changed files with 872 additions and 115 deletions
+1 -1
View File
@@ -157,7 +157,7 @@ jobs:
ls -l fastfetch
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-android-${{ inputs.abi }}
path: ./fastfetch-*.*
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: download benchmark data
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: benchmark-*
path: benchmarks
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
with:
operating_system: dragonflybsd
version: '6.4.2'
@@ -60,7 +60,7 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-dragonfly-amd64
path: ./fastfetch-*.*
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
with:
operating_system: freebsd
version: '15.1'
@@ -61,7 +61,7 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-freebsd-amd64
path: ./fastfetch-*.*
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
with:
operating_system: haiku
version: 'r1beta6'
@@ -53,7 +53,7 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-haiku-amd64
path: ./fastfetch-*.*
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-linux-armv7l
path: ./fastfetch-*.*
+4 -4
View File
@@ -52,7 +52,7 @@ jobs:
- name: Initialize CodeQL
if: inputs.arch == 'amd64'
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
languages: c
@@ -64,7 +64,7 @@ jobs:
- name: perform CodeQL analysis
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
run: ./fastfetch --list-features
@@ -123,13 +123,13 @@ jobs:
cpack -V
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-linux-${{ inputs.arch }}
path: ./fastfetch-*.*
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: benchmark-linux-${{ inputs.arch }}
path: benchmark.json
+1 -1
View File
@@ -67,7 +67,7 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-linux-i686
path: ./fastfetch-*.*
+1 -1
View File
@@ -54,7 +54,7 @@ jobs:
'
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-linux-loong64
path: ./fastfetch-*.*
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-linux-${{ inputs.arch }}
path: ./fastfetch-*.*
+2 -2
View File
@@ -74,13 +74,13 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-macos-universal
path: ./fastfetch-*.*
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: benchmark-macos-universal
path: benchmark.json
+2 -2
View File
@@ -11,7 +11,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -44,7 +44,7 @@ jobs:
shell: alpine.sh {0}
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-musl-amd64
path: ./fastfetch-*.*
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
with:
operating_system: netbsd
version: '10.1'
@@ -59,7 +59,7 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-netbsd-amd64
path: ./fastfetch-*.*
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
with:
operating_system: omnios
version: 'r151058'
@@ -64,7 +64,7 @@ jobs:
cpack -V
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-omnios-amd64
path: ./fastfetch-*.*
+2 -2
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Start VM
uses: cross-platform-actions/action@e0b9770014ba65d5e0815f15b74031c3a635f641 # v1.6.0
uses: cross-platform-actions/action@82668f1fbd89d9c99487604f822cbf6716f124d3 # v1.7.0
with:
operating_system: openbsd
version: '7.9'
@@ -59,7 +59,7 @@ jobs:
run: ctest --output-on-failure
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-openbsd-amd64
path: ./fastfetch-*.*
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
- name: download artifacts
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
if: inputs.ffversion != steps.get_version_release.outputs.release
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: vmactions/solaris-vm@87d3c436511cef802cd1637f86f73b1a97715c8c # v1.4.1
uses: vmactions/solaris-vm@1c166978f5a2c1f87f1c9d4d8dd85ae3d1eff6f7 # v1.4.2
with:
usesh: true
envs: 'CMAKE_BUILD_TYPE'
@@ -40,7 +40,7 @@ jobs:
cpack -V
- name: upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-solaris-amd64
path: ./fastfetch-*.*
+3 -3
View File
@@ -106,7 +106,7 @@ jobs:
- if: github.event_name == 'push' && github.repository == 'fastfetch-cli/fastfetch'
id: upload-unsigned-artifact
name: upload artifacts for signing
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-windows-${{ inputs.arch }}
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
- name: upload true artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: fastfetch-windows-${{ inputs.arch }}
path: ./fastfetch-windows-${{ inputs.arch }}.*
overwrite: true
- name: upload benchmark data
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: benchmark-windows-${{ inputs.arch }}
path: benchmark.json
+20
View File
@@ -1,3 +1,22 @@
# Unreleased
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)
Bugfixes:
* 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 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 AMD GPU name detection in amdgpu.ids when PCI device ID or revision ID has leading zeros. (GPU, Linux)
* Improved uptime detection on SunOS via kstat. (#2674, Uptime, SunOS)
* Fixed substring formatting returning the remaining string when a negative end index is before the start of the string. (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 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)
Logos:
* Added Tiny Core Linux (#2655)
# 2.70.0
Changes:
@@ -42,6 +61,7 @@ Bugfixes:
* On Windows 10 1809 (build 17763) and later, the whole volume's capacity and free space are now reported by default. Set `disk.useAvailable` to `true` to keep reporting the space that is available to the current user, quota included.
Logos:
* Added ArmadaOS logo (#2641)
* Added filled Artix logo (#2631)
* Renamed PostmarketOS to nura (#2626)
+7
View File
@@ -623,6 +623,7 @@ if(LINUX)
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/drm.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
src/detection/displayserver/linux/wayland/wp-color-management-v1-protocol.c
@@ -712,6 +713,7 @@ elseif(ANDROID)
src/detection/diskio/diskio_linux.c
src/detection/displayserver/displayserver_android.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
@@ -800,6 +802,7 @@ elseif(FreeBSD)
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/drm.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
@@ -898,6 +901,7 @@ elseif(NetBSD)
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/drm.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
@@ -983,6 +987,7 @@ elseif(OpenBSD)
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/drm.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
@@ -1243,6 +1248,7 @@ elseif(SunOS)
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/drm.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/wayland/global-output.c
src/detection/displayserver/linux/wayland/kde-output.c
src/detection/displayserver/linux/wayland/wl-output-protocol.c
@@ -1402,6 +1408,7 @@ elseif(GNU)
src/detection/diskio/diskio_nosupport.c
src/detection/displayserver/linux/displayserver_linux.c
src/detection/displayserver/linux/wayland/wayland.c
src/detection/displayserver/linux/wayland/mutter.c
src/detection/displayserver/linux/common.c
src/detection/displayserver/linux/drm.c
src/detection/displayserver/linux/wmde.c
+2 -1
View File
@@ -4142,7 +4142,8 @@
"string": {
"description": "String to print for the separator line",
"type": "string",
"default": "-"
"default": "-",
"minLength": 1
},
"outputColor": {
"description": "Color of the separator line",
+76 -17
View File
@@ -63,6 +63,13 @@ void ffFormatAppendFormatArg(FFstrbuf* buffer, const FFformatarg* formatarg) {
}
}
// strtol() to an int32_t. Values that don't fit are clamped instead of wrapping around,
// so that e.g. 4294967297 can't turn into 1.
static int32_t strToInt32(const char* str, char** pEnd) {
long long value = strtoll(str, pEnd, 10);
return value > INT32_MAX ? INT32_MAX : value < INT32_MIN ? INT32_MIN : (int32_t) value;
}
/**
* @brief parses a string to a uint32_t
*
@@ -79,14 +86,14 @@ static uint32_t getArgumentIndex(const char* placeholderValue, uint32_t numArgs,
if (firstChar >= '0' && firstChar <= '9') {
char* pEnd = nullptr;
uint32_t result = (uint32_t) strtoul(placeholderValue, &pEnd, 10);
unsigned long result = strtoul(placeholderValue, &pEnd, 10);
if (result > numArgs) {
return UINT32_MAX;
}
if (*pEnd != '\0') {
return UINT32_MAX;
}
return result;
return (uint32_t) result;
} else if (ffCharIsEnglishAlphabet(firstChar)) {
for (uint32_t i = 0; i < numArgs; ++i) {
const FFformatarg* arg = &arguments[i];
@@ -343,6 +350,51 @@ static bool skipAnsiEscape(FFstrbuf* in, FFstrbuf* out, FFstrbuf* trailingEscape
return true;
}
// Returns the index of the `}` of the `{<marker>}` that closes the conditional we are in, or the
// length of the format string if there is none. `i` is the index of the `}` that ends the opening
// placeholder. The part to skip may hold conditionals of its own, which have to be passed over.
// Like in `parseFormatString`, only a conditional with a valid argument opens a block.
static uint32_t findConditionalEnd(const FFstrbuf* formatstr, uint32_t i, char marker, uint32_t numArgs, const FFformatarg* arguments) {
FF_STRBUF_AUTO_DESTROY placeholderValue = ffStrbufCreate();
uint32_t depth = 0;
for (++i; i < formatstr->length; ++i) {
if (formatstr->chars[i] != '{') {
continue;
}
++i;
if (i >= formatstr->length) {
break;
}
// double {{ evaluates to a single { and doesn't start a placeholder
if (formatstr->chars[i] == '{') {
continue;
}
uint32_t iEnd = ffStrbufNextIndexC(formatstr, i, '}');
if (formatstr->chars[i] == marker) {
if (iEnd - i == 1) {
if (depth == 0) {
return iEnd;
}
--depth;
} else {
ffStrbufClear(&placeholderValue);
ffStrbufAppendNS(&placeholderValue, iEnd - i - 1, &formatstr->chars[i + 1]);
uint32_t index = getArgumentIndex(placeholderValue.chars, numArgs, arguments);
if (index >= 1 && index <= numArgs) {
++depth;
}
}
}
i = iEnd;
}
return formatstr->length;
}
static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
uint32_t argCounter = 0;
@@ -440,7 +492,7 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
}
// fastforward to the end of the if without printing the in between
i = ffStrbufNextIndexS(formatstr, i, "{?}") + 2; // 2 is the length of "{?}" - 1 because the loop will increment it again directly after continue
i = findConditionalEnd(formatstr, i, '?', numArgs, arguments);
continue;
}
@@ -463,7 +515,7 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
}
// fastforward to the end of the if without printing the in between
i = ffStrbufNextIndexS(formatstr, i, "{/}") + 2; // 2 is the length of "{/}" - 1 because the loop will increment it again directly after continue
i = findConditionalEnd(formatstr, i, '/', numArgs, arguments);
continue;
}
@@ -480,7 +532,7 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
// test for constant or env var, if so evaluate it
if (firstChar == '$') {
char* pend = nullptr;
int32_t indexSigned = (int32_t) strtol(placeholderValue.chars + 1, &pend, 10);
int32_t indexSigned = strToInt32(placeholderValue.chars + 1, &pend);
if (pend == placeholderValue.chars + 1) {
// treat placeholder as an environment variable
char* envValue = getenv(placeholderValue.chars + 1);
@@ -539,20 +591,27 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
skipAnsiEscape(&tempString, buffer, &trailingEscape);
char* pEnd = nullptr;
int32_t start = (int32_t) strtol(pSep + 1, &pEnd, 10);
if (start < 0) {
start = (int32_t) tempString.length + start;
int32_t start = strToInt32(pSep + 1, &pEnd);
bool hasEnd = *pEnd == ',';
int32_t end = 0;
if (hasEnd) {
end = strToInt32(pEnd + 1, &pEnd);
}
if (start >= 0 && (uint32_t) start < tempString.length) {
if (*pEnd == '\0') {
ffStrbufAppendNS(buffer, tempString.length - (uint32_t) start, &tempString.chars[start]);
} else if (*pEnd == ',') {
int32_t end = (int32_t) strtol(pEnd + 1, &pEnd, 10);
if (!*pEnd) {
// Check the whole range before using it, so that a valid range that lies
// outside of the string gives an empty result instead of an invalid placeholder
if (!*pEnd) {
if (start < 0) {
start = (int32_t) tempString.length + start;
}
if (start >= 0 && (uint32_t) start < tempString.length) {
if (!hasEnd) {
ffStrbufAppendNS(buffer, tempString.length - (uint32_t) start, &tempString.chars[start]);
} else {
if (end < 0) {
end = (int32_t) tempString.length + end;
}
if ((uint32_t) end > tempString.length) {
if (end >= 0 && (uint32_t) end > tempString.length) {
end = (int32_t) tempString.length;
}
if (end > start) {
@@ -573,7 +632,7 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
}
} else {
char* pEnd = nullptr;
int32_t truncLength = (int32_t) strtol(pSep + 1, &pEnd, 10);
int32_t truncLength = strToInt32(pSep + 1, &pEnd);
if (*pEnd != '\0') {
*pSep = cSep;
appendInvalidPlaceholder(buffer, "{", &placeholderValue, i, formatstr->length);
@@ -583,7 +642,7 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
bool ellipsis = false;
if (truncLength < 0) {
ellipsis = true;
truncLength = -truncLength;
truncLength = truncLength == INT32_MIN ? INT32_MAX : -truncLength;
}
FF_STRBUF_AUTO_DESTROY tempString = ffStrbufCreate();
+36 -21
View File
@@ -149,6 +149,32 @@ void restoreTerm(void) {
tcsetattr(ftty, TCSAFLUSH, &oldTerm);
}
static const char* waitForTerminalResponse(void) {
#ifndef __APPLE__
const int pollResult = poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS);
if (pollResult <= 0) {
FF_DEBUG("poll(/dev/tty) returned %d after %d ms: %s",
pollResult, FF_IO_TERM_RESP_WAIT_MS, pollResult == 0 ? "timed out" : strerror(errno));
return "poll(/dev/tty) timeout or failed";
}
#else
{
// On macOS, poll(/dev/tty) always returns immediately
// See also https://nathancraddock.com/blog/macos-dev-tty-polling/
fd_set rd;
FD_ZERO(&rd);
FD_SET(ftty, &rd);
const int selectResult = select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 });
if (selectResult <= 0) {
FF_DEBUG("select(/dev/tty) returned %d after %d ms: %s",
selectResult, FF_IO_TERM_RESP_WAIT_MS, selectResult == 0 ? "timed out" : strerror(errno));
return "select(/dev/tty) timeout or failed";
}
}
#endif
return nullptr;
}
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
if (ftty < 0) {
ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
@@ -174,28 +200,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
ffWriteFDData(ftty, strlen(request), request);
// Give the terminal some time to respond
#ifndef __APPLE__
const int pollResult = poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS);
if (pollResult <= 0) {
FF_DEBUG("poll(/dev/tty) returned %d after %d ms: %s",
pollResult, FF_IO_TERM_RESP_WAIT_MS, pollResult == 0 ? "timed out" : strerror(errno));
return "poll(/dev/tty) timeout or failed";
const char* error = waitForTerminalResponse();
if (error) {
return error;
}
#else
{
// On macOS, poll(/dev/tty) always returns immediately
// See also https://nathancraddock.com/blog/macos-dev-tty-polling/
fd_set rd;
FD_ZERO(&rd);
FD_SET(ftty, &rd);
const int selectResult = select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 });
if (selectResult <= 0) {
FF_DEBUG("select(/dev/tty) returned %d after %d ms: %s",
selectResult, FF_IO_TERM_RESP_WAIT_MS, selectResult == 0 ? "timed out" : strerror(errno));
return "select(/dev/tty) timeout or failed";
}
}
#endif
char buffer[1024];
size_t bytesRead = 0;
@@ -204,6 +212,13 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
va_start(args, format);
while (true) {
// The response may arrive in several chunks; a terminal that sends an incomplete
// response would otherwise block `read` forever
if (bytesRead > 0 && (error = waitForTerminalResponse())) {
va_end(args);
return error;
}
ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1);
if (nRead <= 0) {
+43 -23
View File
@@ -414,6 +414,35 @@ void ffListFilesRecursively(const char* path, bool pretty) {
listFilesRecursively(folder.length, &folder, 0, nullptr, pretty);
}
static const char* waitForTerminalResponse(HANDLE hInput) {
while (true) {
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
if (status != STATUS_WAIT_0) {
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
return "NtWaitForSingleObject() failed or timeout";
}
// Ignore all unexpected input events
INPUT_RECORD record;
DWORD len = 0;
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
return nullptr;
}
// Key-up events carry no input for `ReadFile`; leaving one in the buffer would make the
// wait return while the next `ReadFile` still blocks
if (
record.EventType == KEY_EVENT &&
record.Event.KeyEvent.bKeyDown &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
return nullptr;
}
ReadConsoleInputW(hInput, &record, 1, &len);
}
}
const char* ffGetTerminalResponse(const char* request, int nParams, const char* format, ...) {
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
FF_AUTO_CLOSE_FD HANDLE hConin = INVALID_HANDLE_VALUE;
@@ -440,29 +469,10 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
WriteFile(hOutput, request, (DWORD) strlen(request), &bytes, nullptr);
}
while (true) {
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
if (status != STATUS_WAIT_0) {
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
SetConsoleMode(hInput, inputMode);
return "NtWaitForSingleObject() failed or timeout";
}
// Ignore all unexpected input events
INPUT_RECORD record;
DWORD len = 0;
if (!PeekConsoleInputW(hInput, &record, 1, &len)) {
break;
}
if (
record.EventType == KEY_EVENT &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\r' &&
record.Event.KeyEvent.uChar.UnicodeChar != L'\n') {
break;
} else {
ReadConsoleInputW(hInput, &record, 1, &len);
}
const char* error = waitForTerminalResponse(hInput);
if (error) {
SetConsoleMode(hInput, inputMode);
return error;
}
va_list args;
@@ -472,6 +482,16 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
uint32_t bytesRead = 0;
while (true) {
// The response may arrive in several chunks; a terminal that sends an incomplete
// response would otherwise block `ReadFile` forever
if (bytesRead > 0 && (error = waitForTerminalResponse(hInput))) {
va_end(args);
if (hasInputMode) {
SetConsoleMode(hInput, inputMode);
}
return error;
}
DWORD bytes = 0;
BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr);
if (!readOk || bytes == 0) {
@@ -0,0 +1,425 @@
#include "wayland.h"
#if defined(FF_HAVE_WAYLAND) && defined(FF_HAVE_DBUS) && defined(__linux__)
#include "common/arrutil.h"
#include "common/dbus.h"
#include "common/debug.h"
#include "common/edidHelper.h"
#include "common/strutil.h"
#include <string.h>
// Read the display configuration of GNOME / Mutter and Cinnamon / Muffin, including the raw EDID
// data, instead of guessing the DRM connector by matching the wl_output name (#...).
// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml
// MetaMonitorColorMode
// https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/backends/meta-monitor-manager-private.h
#define FF_MUTTER_COLOR_MODE_BT2100 1
// Muffin (Cinnamon) exposes the very same interface as Mutter (GNOME), only the names differ
typedef struct FFMutterDisplayConfig {
const char* busName;
const char* objectPath;
const char* interface;
} FFMutterDisplayConfig;
static const FFMutterDisplayConfig mutterDisplayConfigs[] = {
{
.busName = "org.gnome.Mutter.DisplayConfig",
.objectPath = "/org/gnome/Mutter/DisplayConfig",
.interface = "org.gnome.Mutter.DisplayConfig",
},
{
.busName = "org.cinnamon.Muffin.DisplayConfig",
.objectPath = "/org/cinnamon/Muffin/DisplayConfig",
.interface = "org.cinnamon.Muffin.DisplayConfig",
},
};
static inline DBusMessage* mutterCall(FFDBusData* dbus, const FFMutterDisplayConfig* config, const char* method) {
return ffDBusGetMethodReply(dbus, config->busName, config->objectPath, config->interface, method, nullptr, nullptr);
}
// Returns the first display configuration interface that responds, or nullptr
static const FFMutterDisplayConfig* mutterFindConfig(FFDBusData* dbus) {
for (uint32_t i = 0; i < ARRAY_SIZE(mutterDisplayConfigs); ++i) {
DBusMessage* reply = mutterCall(dbus, &mutterDisplayConfigs[i], "GetCurrentState");
if (reply != nullptr) {
dbus->lib->ffdbus_message_unref(reply);
return &mutterDisplayConfigs[i];
}
}
return nullptr;
}
static FFDisplayResult* mutterFindDisplay(FFDisplayServerResult* result, const char* connector) {
if (connector == nullptr || *connector == '\0') {
return nullptr;
}
const uint64_t id = ffWaylandGenerateIdFromName(connector);
FF_LIST_FOR_EACH (FFDisplayResult, display, result->displays) {
if (display->id == id) {
display->platformApi = "wayland-mutter";
return display;
}
}
return nullptr;
}
// Point `value` to the variant stored under `key` in the `a{sv}` dictionary `dict`
static bool mutterDictGet(FFDBusData* dbus, DBusMessageIter* dict, const char* key, DBusMessageIter* value) {
if (dbus->lib->ffdbus_message_iter_get_arg_type(dict) != DBUS_TYPE_ARRAY) {
return false;
}
DBusMessageIter entries;
dbus->lib->ffdbus_message_iter_recurse(dict, &entries);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry;
dbus->lib->ffdbus_message_iter_recurse(&entries, &entry);
if (dbus->lib->ffdbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) {
const char* entryKey = nullptr;
dbus->lib->ffdbus_message_iter_get_basic(&entry, &entryKey);
if (entryKey && ffStrEquals(entryKey, key) && dbus->lib->ffdbus_message_iter_next(&entry)) {
*value = entry;
return true;
}
}
if (!dbus->lib->ffdbus_message_iter_next(&entries)) {
break;
}
}
return false;
}
// Whether the `au` array contained in the variant `variant` contains `needle`
static bool mutterVariantContainsUint32(FFDBusData* dbus, DBusMessageIter* variant, uint32_t needle) {
if (dbus->lib->ffdbus_message_iter_get_arg_type(variant) != DBUS_TYPE_VARIANT) {
return false;
}
DBusMessageIter array;
dbus->lib->ffdbus_message_iter_recurse(variant, &array);
if (dbus->lib->ffdbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) {
return false;
}
DBusMessageIter items;
dbus->lib->ffdbus_message_iter_recurse(&array, &items);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&items) == DBUS_TYPE_UINT32) {
uint32_t item = 0;
dbus->lib->ffdbus_message_iter_get_basic(&items, &item);
if (item == needle) {
return true;
}
if (!dbus->lib->ffdbus_message_iter_next(&items)) {
break;
}
}
return false;
}
// Copy the `ay` byte array contained in the variant `variant` into `data`
static bool mutterVariantGetBytes(FFDBusData* dbus, DBusMessageIter* variant, uint8_t* data, uint32_t capacity, uint32_t* length) {
uint32_t count = 0;
if (dbus->lib->ffdbus_message_iter_get_arg_type(variant) == DBUS_TYPE_VARIANT) {
DBusMessageIter array;
dbus->lib->ffdbus_message_iter_recurse(variant, &array);
if (dbus->lib->ffdbus_message_iter_get_arg_type(&array) == DBUS_TYPE_ARRAY) {
DBusMessageIter items;
dbus->lib->ffdbus_message_iter_recurse(&array, &items);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&items) == DBUS_TYPE_BYTE) {
uint8_t byte = 0;
dbus->lib->ffdbus_message_iter_get_basic(&items, &byte);
if (count < capacity) {
data[count] = byte;
}
++count;
if (!dbus->lib->ffdbus_message_iter_next(&items)) {
break;
}
}
}
}
*length = count;
return count > 0 && count <= capacity;
}
static void mutterApplyEdid(FFDisplayResult* display, const uint8_t* edid, uint32_t length) {
FF_STRBUF_AUTO_DESTROY edidName = ffStrbufCreate();
if (ffEdidGetName(edid, &edidName) && edidName.length > 0) {
ffStrbufSet(&display->name, &edidName);
}
if (display->serial.length == 0) {
ffEdidGetSerial(edid, &display->serial);
}
if (display->manufactureYear == 0) {
ffEdidGetManufactureDate(edid, &display->manufactureYear, &display->manufactureWeek);
}
if (display->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN) {
display->hdrStatus = ffEdidGetHdrCompatible(edid, length)
? FF_DISPLAY_HDR_STATUS_SUPPORTED
: FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
}
if (display->preferredWidth == 0) {
uint32_t width = 0, height = 0;
double refreshRate = 0;
ffEdidGetPreferredResolutionAndRefreshRate(edid, &width, &height, &refreshRate);
display->preferredWidth = width;
display->preferredHeight = height;
display->preferredRefreshRate = refreshRate;
}
}
static void mutterApplyMonitorProperties(FFDBusData* dbus, DBusMessageIter* properties, FFDisplayResult* display) {
DBusMessageIter value;
// "is-builtin" is authoritative, unlike ffdsGetDisplayType() which guesses from the connector name
if (mutterDictGet(dbus, properties, "is-builtin", &value)) {
bool builtin = false;
if (ffDBusGetBool(dbus, &value, &builtin)) {
display->type = builtin ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL;
}
}
bool hdrEnabled = false;
if (mutterDictGet(dbus, properties, "color-mode", &value)) {
uint64_t colorMode = 0;
hdrEnabled = ffDBusGetUint(dbus, &value, &colorMode) && colorMode == FF_MUTTER_COLOR_MODE_BT2100;
}
bool hdrSupported = false;
if (mutterDictGet(dbus, properties, "supported-color-modes", &value)) {
hdrSupported = mutterVariantContainsUint32(dbus, &value, FF_MUTTER_COLOR_MODE_BT2100);
}
if (hdrEnabled) {
display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
} else if (hdrSupported && display->hdrStatus == FF_DISPLAY_HDR_STATUS_UNKNOWN) {
display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
}
}
static void mutterParseCurrentState(FFDBusData* dbus, DBusMessage* reply, FFDisplayServerResult* result) {
DBusMessageIter root;
if (!dbus->lib->ffdbus_message_iter_init(reply, &root)) {
return;
}
// (u serial, a(monitors) monitors, a(logical_monitors) logical_monitors, a{sv} properties)
if (!dbus->lib->ffdbus_message_iter_next(&root)) { // monitors
return;
}
if (dbus->lib->ffdbus_message_iter_get_arg_type(&root) == DBUS_TYPE_ARRAY) {
DBusMessageIter monitors;
dbus->lib->ffdbus_message_iter_recurse(&root, &monitors);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&monitors) == DBUS_TYPE_STRUCT) {
DBusMessageIter monitor;
dbus->lib->ffdbus_message_iter_recurse(&monitors, &monitor);
// ((ssss) monitor_specs, a(siiddada{sv}) modes, a{sv} properties)
if (dbus->lib->ffdbus_message_iter_get_arg_type(&monitor) == DBUS_TYPE_STRUCT) {
DBusMessageIter specs;
dbus->lib->ffdbus_message_iter_recurse(&monitor, &specs);
const char* connector = nullptr;
if (dbus->lib->ffdbus_message_iter_get_arg_type(&specs) == DBUS_TYPE_STRING) {
dbus->lib->ffdbus_message_iter_get_basic(&specs, &connector);
}
FFDisplayResult* display = mutterFindDisplay(result, connector);
if (display) {
// (connector, vendor, product, serial)
DBusMessageIter serialSpec = specs;
if (display->serial.length == 0 &&
dbus->lib->ffdbus_message_iter_next(&serialSpec) && // vendor
dbus->lib->ffdbus_message_iter_next(&serialSpec) && // product
dbus->lib->ffdbus_message_iter_next(&serialSpec) && // serial
dbus->lib->ffdbus_message_iter_get_arg_type(&serialSpec) == DBUS_TYPE_STRING) {
const char* serial = nullptr;
dbus->lib->ffdbus_message_iter_get_basic(&serialSpec, &serial);
if (serial && !ffStrEquals(serial, "unknown")) {
ffStrbufSetS(&display->serial, serial);
}
}
// Skip `modes`, then read `properties`
DBusMessageIter properties = monitor;
if (dbus->lib->ffdbus_message_iter_next(&properties) &&
dbus->lib->ffdbus_message_iter_next(&properties) &&
dbus->lib->ffdbus_message_iter_get_arg_type(&properties) == DBUS_TYPE_ARRAY) {
mutterApplyMonitorProperties(dbus, &properties, display);
}
}
}
dbus->lib->ffdbus_message_iter_next(&monitors);
}
}
// logical_monitors -> primary flag
if (!dbus->lib->ffdbus_message_iter_next(&root) ||
dbus->lib->ffdbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) {
return;
}
DBusMessageIter logicalMonitors;
dbus->lib->ffdbus_message_iter_recurse(&root, &logicalMonitors);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&logicalMonitors) == DBUS_TYPE_STRUCT) {
DBusMessageIter logicalMonitor;
dbus->lib->ffdbus_message_iter_recurse(&logicalMonitors, &logicalMonitor);
// (i x, i y, d scale, u transform, b primary, a(ssss) monitors, a{sv} properties)
int index = 0;
bool primary = false;
do {
const int argType = dbus->lib->ffdbus_message_iter_get_arg_type(&logicalMonitor);
if (index == 4 && argType == DBUS_TYPE_BOOLEAN) {
dbus_bool_t value = 0;
dbus->lib->ffdbus_message_iter_get_basic(&logicalMonitor, &value);
primary = value != 0;
} else if (index == 5 && primary && argType == DBUS_TYPE_ARRAY) {
DBusMessageIter logicalMonitorMonitors;
dbus->lib->ffdbus_message_iter_recurse(&logicalMonitor, &logicalMonitorMonitors);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&logicalMonitorMonitors) == DBUS_TYPE_STRUCT) {
DBusMessageIter specs;
dbus->lib->ffdbus_message_iter_recurse(&logicalMonitorMonitors, &specs);
if (dbus->lib->ffdbus_message_iter_get_arg_type(&specs) == DBUS_TYPE_STRING) {
const char* connector = nullptr;
dbus->lib->ffdbus_message_iter_get_basic(&specs, &connector);
FFDisplayResult* display = mutterFindDisplay(result, connector);
if (display) {
display->primary = true;
}
}
dbus->lib->ffdbus_message_iter_next(&logicalMonitorMonitors);
}
}
++index;
} while (dbus->lib->ffdbus_message_iter_next(&logicalMonitor));
dbus->lib->ffdbus_message_iter_next(&logicalMonitors);
}
}
static void mutterParseResources(FFDBusData* dbus, DBusMessage* reply, FFDisplayServerResult* result) {
DBusMessageIter root;
if (!dbus->lib->ffdbus_message_iter_init(reply, &root)) {
return;
}
// (u serial, a(crtcs) crtcs, a(outputs) outputs, a(modes) modes, i max_screen_width, i max_screen_height)
DBusMessageIter outputs;
if (!dbus->lib->ffdbus_message_iter_next(&root) || // crtcs
!dbus->lib->ffdbus_message_iter_next(&root) || // outputs
dbus->lib->ffdbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) {
return;
}
dbus->lib->ffdbus_message_iter_recurse(&root, &outputs);
while (dbus->lib->ffdbus_message_iter_get_arg_type(&outputs) == DBUS_TYPE_STRUCT) {
DBusMessageIter output;
dbus->lib->ffdbus_message_iter_recurse(&outputs, &output);
// (u id, x output_id, i crtc_index, au crtcs, s name, au modes, au clones, a{sv} properties)
FFDisplayResult* display = nullptr;
int index = 0;
do {
const int argType = dbus->lib->ffdbus_message_iter_get_arg_type(&output);
if (index == 4 && argType == DBUS_TYPE_STRING) {
const char* connector = nullptr;
dbus->lib->ffdbus_message_iter_get_basic(&output, &connector);
display = mutterFindDisplay(result, connector);
} else if (index == 7 && display != nullptr && argType == DBUS_TYPE_ARRAY) {
DBusMessageIter value;
if (mutterDictGet(dbus, &output, "edid", &value)) {
uint8_t edid[1024];
uint32_t length = 0;
if (mutterVariantGetBytes(dbus, &value, edid, ARRAY_SIZE(edid), &length) && ffEdidIsValid(edid, length)) {
mutterApplyEdid(display, edid, length);
}
}
}
++index;
} while (dbus->lib->ffdbus_message_iter_next(&output));
dbus->lib->ffdbus_message_iter_next(&outputs);
}
}
// Whether org.gnome.Mutter.DisplayConfig (GNOME) or org.cinnamon.Muffin.DisplayConfig (Cinnamon)
// is reachable. Used to decide if the wl_output name based DRM connector matching
// (matchDrmConnector) can be skipped.
bool ffdsMutterAvailable(void) {
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
if (ffDBusLoadData(DBUS_BUS_SESSION, &dbus) != nullptr) {
return false;
}
return mutterFindConfig(&dbus) != nullptr;
}
// 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.
bool ffdsConnectMutter(FFDisplayServerResult* result) {
FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
const char* error = ffDBusLoadData(DBUS_BUS_SESSION, &dbus);
if (error) {
FF_DEBUG("ffDBusLoadData() failed: %s", error);
return false;
}
const FFMutterDisplayConfig* config = mutterFindConfig(&dbus);
if (config == nullptr) {
FF_DEBUG("No Mutter / Muffin DisplayConfig interface found");
return false;
}
DBusMessage* reply = mutterCall(&dbus, config, "GetCurrentState");
if (reply == nullptr) {
FF_DEBUG("GetCurrentState() failed");
return false;
}
mutterParseCurrentState(&dbus, reply, result);
dbus.lib->ffdbus_message_unref(reply);
// GetCurrentState() does not expose the EDID data, so fall back to the legacy GetResources()
reply = mutterCall(&dbus, config, "GetResources");
if (reply != nullptr) {
mutterParseResources(&dbus, reply, result);
dbus.lib->ffdbus_message_unref(reply);
} else {
FF_DEBUG("GetResources() failed");
}
return true;
}
#endif
@@ -157,7 +157,7 @@ void ffWaylandOutputNameListener(void* data, [[maybe_unused]] void* output, cons
display->type = ffdsGetDisplayType(name);
#if __linux__
if (!display->edidName.length) {
if (!display->edidName.length && !display->parent->useMutter) {
matchDrmConnector(name, display);
}
#endif
@@ -282,6 +282,14 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) {
waylandDetectWM(ffwl_display_get_fd(data.display), result);
#if defined(FF_HAVE_DBUS) && defined(__linux__)
// gnome-shell (Mutter) and cinnamon (Muffin) expose the monitor configuration via D-Bus,
// including the raw EDID. Prefer it over matching the wl_output name against the DRM connectors.
data.useMutter =
(ffStrbufEqualS(&result->wmProcessName, "gnome-shell") || ffStrbufEqualS(&result->wmProcessName, "cinnamon")) &&
ffdsMutterAvailable();
#endif
struct wl_proxy* registry = ffwl_proxy_marshal_constructor((struct wl_proxy*) data.display, WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, nullptr);
if (registry == nullptr) {
ffwl_display_disconnect(data.display);
@@ -309,7 +317,16 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) {
data.ffwl_proxy_destroy(registry);
ffwl_display_disconnect(data.display);
{
bool primaryDetected = false;
#if defined(FF_HAVE_DBUS) && defined(__linux__)
if (data.useMutter) {
// Mutter / Muffin reports the primary monitor and the raw EDID directly
primaryDetected = ffdsConnectMutter(result);
}
#endif
// Fallback for compositors that do not expose the monitor configuration, or when it could not be read
if (!primaryDetected) {
const char* fileName = ffStrbufEqualS(&result->wmProcessName, "gnome-shell")
? "monitors.xml"
: ffStrbufEqualS(&result->wmProcessName, "cinnamon")
@@ -37,6 +37,7 @@ typedef struct WaylandData {
WaylandProtocolType protocolType;
struct wl_proxy* zxdgOutputManager;
struct wl_proxy* wpColorManager;
bool useMutter; // gnome-shell: read the display configuration from org.gnome.Mutter.DisplayConfig instead of guessing it
} WaylandData;
typedef struct WaylandDisplay {
@@ -92,4 +93,9 @@ const char* ffWaylandHandleKdeOutputOrder(WaylandData* wldata, struct wl_registr
const char* ffWaylandHandleZxdgOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
const char* ffWaylandHandleWpColor(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version);
#if defined(FF_HAVE_DBUS) && defined(__linux__)
bool ffdsMutterAvailable(void);
bool ffdsConnectMutter(FFDisplayServerResult* result);
#endif
#endif
+1 -1
View File
@@ -272,7 +272,7 @@ static bool loadAmdGpuIdsInc(uint16_t deviceId, uint8_t revision, FFGPUResult* g
static void parseAmdGpuIdsFile(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) {
char query[32];
snprintf(query, ARRAY_SIZE(query), "%X,\t%X,", (unsigned) deviceId, (unsigned) revision);
snprintf(query, ARRAY_SIZE(query), "%04X,\t%02X,", (unsigned) deviceId, (unsigned) revision);
#ifdef FF_CUSTOM_AMDGPU_IDS_PATH
FF_DEBUG("Searching AMD GPU IDs at custom path '%s' for device=0x%04x revision=0x%02x", FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), deviceId, revision);
ffParsePropFile(FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), query, &gpu->name);
+29 -11
View File
@@ -1,19 +1,37 @@
#include "uptime.h"
#include "common/debug.h"
#include "common/time.h"
#include <utmpx.h>
#include <kstat.h>
static inline void kstatFreeWrap(kstat_ctl_t** pkc) {
assert(pkc);
if (*pkc) {
kstat_close(*pkc);
}
}
const char* ffDetectUptime(FFUptimeResult* result) {
struct utmpx* ut;
setutxent();
while (nullptr != (ut = getutxent())) {
if (ut->ut_type == BOOT_TIME) {
result->bootTime = (uint64_t) ut->ut_tv.tv_sec * 1000 + (uint64_t) ut->ut_tv.tv_usec / 1000000;
result->uptime = ffTimeGetNow() - result->bootTime;
break;
}
[[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
if (!kc) {
FF_DEBUG("kstat_open() failed");
return "kstat_open() failed";
}
endutxent();
kstat_t* ks = kstat_lookup(kc, "unix", 0, "system_misc");
if (!ks || kstat_read(kc, ks, nullptr) < 0) {
FF_DEBUG("kstat_lookup(unix:0:system_misc) failed");
return "kstat_lookup(unix:0:system_misc) failed";
}
kstat_named_t* bootTime = (kstat_named_t*) kstat_data_lookup(ks, "boot_time");
if (!bootTime) {
FF_DEBUG("kstat_data_lookup(boot_time) failed");
return "kstat_data_lookup(boot_time) failed";
}
result->bootTime = (uint64_t) bootTime->value.ui32 * 1000;
result->uptime = ffTimeGetNow() - result->bootTime;
return nullptr;
}
+4 -4
View File
@@ -52,15 +52,15 @@ const char* ffDetectWifi(FFlist* result) {
if (inf.ssid) { // https://developer.apple.com/forums/thread/732431
ffStrbufAppendS(&item->conn.ssid, inf.ssid.UTF8String);
} else if (networkProfile.ssid) {
ffStrbufSetStatic(&item->conn.ssid, inf.configuration.networkProfiles.firstObject.ssid.UTF8String);
} else if (networkProfile.ssid) { // Dead in macOS 27.2
ffStrbufSetStatic(&item->conn.ssid, networkProfile.ssid.UTF8String);
} else {
ffStrbufSetStatic(&item->conn.ssid, "<redacted>"); // https://developer.apple.com/forums/thread/732431
ffStrbufSetStatic(&item->conn.ssid, "<redacted>");
}
if (inf.bssid) {
ffStrbufAppendS(&item->conn.bssid, inf.bssid.UTF8String);
} else if (networkProfile.bssidList) {
} else if (networkProfile.bssidList) { // Dead in macOS 27.2
ffStrbufSetStatic(&item->conn.bssid, [networkProfile.bssidList.firstObject[@"BSSID"] UTF8String]);
} else {
ffStrbufSetStatic(&item->conn.bssid, "<redacted>");
+30
View File
@@ -615,6 +615,17 @@ static const FFlogo A[] = {
.colorTitle = FF_COLOR_FG_DEFAULT,
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_ARMADA
// ArmadaOS
{
.names = { "Armada" },
.lines = FASTFETCH_DATATEXT_LOGO_ARMADA,
.colors = {
FF_COLOR_FG_DEFAULT,
FF_COLOR_FG_BLUE,
},
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_ARMBIAN
// Armbian
{
@@ -845,6 +856,25 @@ static const FFlogo A[] = {
.colorTitle = FF_COLOR_FG_DEFAULT,
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_ASTROS
// AstrOS
{
.names = { "AstrOS" },
.lines = FASTFETCH_DATATEXT_LOGO_ASTROS,
.colors = {
FF_COLOR_FG_RGB "54;19;170",
FF_COLOR_FG_RGB "73;32;182",
FF_COLOR_FG_RGB "91;46;193",
FF_COLOR_FG_RGB "110;59;205",
FF_COLOR_FG_RGB "129;73;216",
FF_COLOR_FG_RGB "148;86;228",
FF_COLOR_FG_RGB "166;100;239",
FF_COLOR_FG_RGB "185;113;251",
},
.colorKeys = FF_COLOR_FG_RGB "148;86;228",
.colorTitle = FF_COLOR_FG_RGB "185;113;251",
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_JANUSLINUX
// Ataraxia
{
+17
View File
@@ -0,0 +1,17 @@
#
#\
/##
###
####
##### #\
###### ##
####### ##\
######## |###
########/ ####\
/######### ####|
##########/ /#####
############ #####|
#############/ ######/
##############/ #######
#######^^^^^^^^/ /######/
.#^'' `'^#'
+20
View File
@@ -0,0 +1,20 @@
$7▁▃▅▅▅▅▃$8▁
$7▗████████▖
$6▟$7██████████▙
$6▟███████$7█████▙
$6▗████████████████$7▖
$5▗██$6████████████████▖
$5▟█████████$6███████████▙
$5▟████████▛▔▔▔▔▜██$6██████▙ ▝▀▀▀▇▇▅▃
$4▗▇███$5█████▘ ▝████████$6█▖ ▜██▙
$4▗█████████▘ $5▝█████████▖ $6▟███
$4▄█████████▘ ▐▌ $5▝█████████▙ ▟███$6▌
$3▟████$4████▛ ▁▂▃▅██▅▃▂▂ ▜█$5███████▋▁▄████▘
$3▗▟████████▛ $4▔▀██▀▔▔ ▜████▛▀▂$5▅████▀
$3▗█████████▀ ▝▘ $4▀▀▂▃▆████▛▀
$2▂ ▃██████$3███▘ ▁▂$4▄▅▆█████▀▀▂▅█▙
$2▟█████████▘▄▄▄$3▄▄▄▄▄▄▅▅▅▆▇▇██████▀▀▀▂$4▄▆█████▙
$1▟$2████████▛▘▝▀▀▀▀▀▀▀▀▀▀$3▀▀▀▀▀▀▔▔▔ ▝███████$4██▙▖
$1████████$2█▛ $3▜█████████
$1▜███████▛ $3▜███████▛
$1▔▀▀▀▀▀▔ $2▔▀$3▀▀▀▀▔
+11
View File
@@ -85,6 +85,17 @@ static const FFlogo T[] = {
},
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_TINYCORE
// TinyCore
{
.names = { "TinyCore" },
.lines = FASTFETCH_DATATEXT_LOGO_TINYCORE,
.colors = {
FF_COLOR_FG_BLUE,
FF_COLOR_FG_LIGHT_WHITE,
},
},
#endif
#ifdef FASTFETCH_DATATEXT_LOGO_TORIZONCORE
// Torizon OS
{
+10
View File
@@ -0,0 +1,10 @@
$1 .-~~~~~~~-.
$1 .' $2tiny$1 '.
$1 / / \
$1 ; / ;
$1 | / |
$1 | / |
$1 ; / $2micro$1 ;
$1 \ / /
$1 '. .'
$1 '-.......-'
+5 -2
View File
@@ -143,10 +143,13 @@ static void logoLineCacheBuild(FFLogoLineCacheState* cache, const char* data, bo
if (doColorReplacement && *data == '$') {
++data;
if (*data == '$' || *data == '\0') {
// `$$` is an escaped dollar; a `$` ending a line or the logo is a plain one
if (*data == '$' || *data == '\0' || *data == '\n' || *data == '\r') {
ffStrbufAppendC(&line, '$');
++lineWidth;
++data;
if (*data == '$') {
++data;
}
continue;
}
+8
View File
@@ -80,6 +80,14 @@ void ffParseSeparatorJsonObject(FFSeparatorOptions* options, yyjson_val* module)
}
if (unsafe_yyjson_equals_str(key, "string")) {
if (!yyjson_is_str(val)) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Separator), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property 'string' must be a string");
continue;
}
if (unsafe_yyjson_get_len(val) == 0) {
ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Separator), 0, nullptr, FF_PRINT_TYPE_NO_CUSTOM_KEY, "Property 'string' must not be empty");
continue;
}
ffStrbufSetJsonVal(&options->string, val);
continue;
}
+1 -1
View File
@@ -60,7 +60,7 @@ void ffGenerateUptimeJsonConfig(FFUptimeOptions* options, yyjson_mut_doc* doc, y
}
bool ffGenerateUptimeJsonResult([[maybe_unused]] FFUptimeOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) {
FFUptimeResult result;
FFUptimeResult result = {};
const char* error = ffDetectUptime(&result);
if (error) {
+60
View File
@@ -17,6 +17,19 @@ static void verify(const char* format, const char* arg, const char* expected, in
#define VERIFY(format, argument, expected) verify((format), (argument), (expected), __LINE__)
static void verify2(const char* format, const char* arg1, const char* arg2, const char* expected, int lineNo) {
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
FF_STRBUF_AUTO_DESTROY formatter = ffStrbufCreateStatic(format);
const FFformatarg arguments[] = { { .type = FF_ARG_TYPE_STRING, arg1 }, { .type = FF_ARG_TYPE_STRING, arg2 } };
ffParseFormatString(&result, &formatter, 2, arguments);
if (!ffStrbufEqualS(&result, expected)) {
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] %s: expected \"%s\", got \"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET, lineNo, format, expected, result.chars);
exit(1);
}
}
#define VERIFY2(format, argument1, argument2, expected) verify2((format), (argument1), (argument2), (expected), __LINE__)
int main(void) {
instance.config.display.pipe = true;
@@ -96,9 +109,14 @@ int main(void) {
VERIFY("output({1~10,10})", "12345 67890", "output()");
VERIFY("output({1~10,5})", "12345 67890", "output()");
VERIFY("output({1~3,-3})", "12345 67890", "output(45 67)");
VERIFY("output({1~0,-12})", "12345 67890", "output()");
VERIFY("output({1~-5,-3})", "12345 67890", "output(67)");
VERIFY("output({1~-0,10})", "12345 67890", "output(12345 6789)");
VERIFY("output({1~-3,-5})", "12345 67890", "output()");
VERIFY("output({1~11,12})", "12345 67890", "output()"); // Same as {1~11}
VERIFY("output({1~20,30})", "12345 67890", "output()"); // Same as {1~20}
VERIFY("output({1~-50,3})", "12345 67890", "output()"); // Same as {1~-50}
VERIFY("output({1~-50,-1})", "12345 67890", "output()"); // Same as {1~-50}
VERIFY("output({1~})", "12345 67890", "output(12345 67890)"); // Same as {1~0}
VERIFY("output({1~-0})", "12345 67890", "output(12345 67890)"); // Same as {1~0}
VERIFY("output({1~,-1})", "12345 67890", "output(12345 6789)"); // Same as {1~0,-1}
@@ -122,6 +140,28 @@ int main(void) {
VERIFY("output({?1}OK{?}{/1}NOT OK{/})", "", "output(NOT OK)");
}
{
// a skipped conditional may hold conditionals of its own
VERIFY2("{?1}A{?2}B{?}C{?}D", "x", "x", "ABCD");
VERIFY2("{?1}A{?2}B{?}C{?}D", "x", "", "ACD");
VERIFY2("{?1}A{?2}B{?}C{?}D", "", "x", "D");
VERIFY2("{?1}A{?2}B{?}C{?}D", "", "", "D");
VERIFY2("{/1}A{/2}B{/}C{/}D", "", "", "ABCD");
VERIFY2("{/1}A{/2}B{/}C{/}D", "", "x", "ACD");
VERIFY2("{/1}A{/2}B{/}C{/}D", "x", "", "D");
VERIFY2("{/1}A{/2}B{/}C{/}D", "x", "x", "D");
VERIFY2("{?1}A{?1}B{?1}C{?}D{?}E{?}F", "", "", "F");
VERIFY2("{?1}{/2}A{/}B{?}C", "", "", "C");
// a conditional with an invalid argument does not open a block
VERIFY2("{?1}A{?3}B{?}C", "", "x", "C");
VERIFY2("{?1}A{?3}B{?}C{?}D", "", "x", "C{?}D");
// an escaped brace does not start a placeholder
VERIFY2("{?1}{{?}}A{?}B", "", "", "B");
VERIFY2("{?1}{{?2}A{?}B", "", "x", "B");
}
{
VERIFY("output({1:3})", "\e[1m12345", "output(\e[1m123)");
VERIFY("output({1:-3})", "\e[1m12345", "output(\e[1m123…)");
@@ -164,6 +204,22 @@ int main(void) {
VERIFY("output({1~4})", "\e[?25l12345", "output(\e[?25l5)");
}
{
// numbers that don't fit in 32 bits must not wrap around to a valid value
VERIFY("output({4294967296})", "x", "output({4294967296})");
VERIFY("output({4294967297})", "x", "output({4294967297})");
VERIFY("output({99999999999999999999})", "x", "output({99999999999999999999})");
VERIFY("output({1~4294967297})", "12345 67890", "output()");
VERIFY("output({1~-4294967297})", "12345 67890", "output()");
VERIFY("output({1~0,4294967296})", "12345 67890", "output(12345 67890)");
VERIFY("output({1:4294967297})", "12345 67890", "output(12345 67890)");
VERIFY("output({1:-4294967296})", "12345 67890", "output(12345 67890)");
// the length is negated for the ellipsis, which overflows for the smallest int32
VERIFY("output({1:-2147483648})", "12345 67890", "output(12345 67890)");
VERIFY("output({1:-2147483647})", "12345 67890", "output(12345 67890)");
}
#ifndef _WIN32 // Windows doesn't have setenv
{
ffListInit(&instance.config.display.constants);
@@ -180,6 +236,10 @@ int main(void) {
VERIFY("output({$FF_INVAL})", "", "output({$FF_INVAL})");
VERIFY("output({$9}{$0}${-9})", "", "output({$9}{$0}${-9})");
VERIFY("output({$1NO})", "", "output({$1NO})");
// numbers that don't fit in 32 bits must not wrap around to a valid index
VERIFY("output({$4294967297})", "", "output({$4294967297})");
VERIFY("output({$-4294967297})", "", "output({$-4294967297})");
ffListDestroy(&instance.config.display.constants);
}
#endif
+10
View File
@@ -1,5 +1,6 @@
#include "logo/logo.h"
#include "common/textModifier.h"
#include "fastfetch.h"
#include <stdio.h>
#include <stdlib.h>
@@ -55,6 +56,15 @@ int main(void) {
VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_TOP), "top") == 0);
VERIFY(strcmp(ffLogoPositionToString(FF_LOGO_POSITION_RIGHT), "right") == 0);
// A `$` ending a line or the logo is a plain dollar: it keeps the line break and the terminator.
instance.config.display.pipe = true;
ffLogoPrintChars("ab$\ncd", true);
VERIFY(instance.state.logoHeight == 1 && instance.state.logoWidth == 3);
ffLogoPrintChars("ab$", true);
VERIFY(instance.state.logoHeight == 0 && instance.state.logoWidth == 3);
ffLogoPrintChars("a$1b$$c", true);
VERIFY(instance.state.logoWidth == 4);
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
return 0;
}